init
This commit is contained in:
commit
a3cce2df73
31
Controller/Admin.php
Normal file
31
Controller/Admin.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Drone\Controller;
|
||||
|
||||
use Cockpit\AuthController;
|
||||
|
||||
/**
|
||||
* Admin controller class.
|
||||
*/
|
||||
class Admin extends AuthController
|
||||
{
|
||||
|
||||
/**
|
||||
* Default index controller.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (!$this->app->module('cockpit')->hasaccess('drone', 'manage.view')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->app->module('drone')->fetchDeploys();
|
||||
|
||||
return $this->render('drone:views/deploys/index.php', [
|
||||
'deploys' => $data['deploys'] ?? [],
|
||||
'building' => $data['building'] ?? FALSE,
|
||||
'build' => $data['build']
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Paulo Gomes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Cockpit CMS Drone Deploys Addon
|
||||
|
||||
This addons is a modified version of the addon [CockpitCMS-Netlify](https://github.com/pauloamgomes/CockpitCMS-Netlify).
|
||||
|
||||
It provides an integration with Drone CI promote feature to trigger a pipeline run. You can use it to deploy a website based on data in Cockpit CMS and built with a static site generator.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Confirm that you have Cockpit CMS (Next branch) installed and working.
|
||||
2. Download and extract to 'your-cockpit-docroot/addons' (e.g. cockpitcms/addons/Drone, the addon folder name must be Drone)
|
||||
3. Drone icon will apear, if configuration is completed.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Ensure that from your Drone account you have an access token and permissions to promote a build for the project and branch, you want to use.
|
||||
2. Edit Cockpit config/config.yaml and add a new entry for drone like below:
|
||||
|
||||
```yaml
|
||||
drone:
|
||||
url: https://drone.yourserver.de
|
||||
token: <your drone access token>
|
||||
owner: <the owner of the git repository>
|
||||
project: <the git project>
|
||||
branch: <the branch in your git repository for the deployment>
|
||||
environment: <the environment used in your drone pipeline>
|
||||
build: <the build number used as base for deployment> # leave empty and the addon will use the latest successfull build based on a push event
|
||||
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
There are just two permissions:
|
||||
|
||||
- **manage.view** - provides access to the Drone deploy list
|
||||
- **manage.deploy** - provides access to trigger a new deploy
|
||||
|
||||
## Usage
|
||||
|
||||
Having the configuration defined accessing the Drone deploys page (/drone/deploys) a list of latest (limited to 50) deploys is displayed:
|
||||
|
||||
To trigger a new deploy just hist the Deploy button an confirm the action.
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright 2019 Sebastian Frank under the MIT license.
|
||||
|
29
admin.php
Normal file
29
admin.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Addon admin functions.
|
||||
*/
|
||||
|
||||
// Module ACL definitions.
|
||||
$this("acl")->addResource('drone', [
|
||||
'manage.view',
|
||||
'manage.deploy',
|
||||
]);
|
||||
|
||||
$app->on('admin.init', function () use ($app) {
|
||||
if ($app->config['drone']) {
|
||||
// Bind admin routes.
|
||||
$this->bindClass('Drone\\Controller\\Admin', 'drone/deploys');
|
||||
|
||||
if ($app->module('cockpit')->hasaccess('drone', 'deploys.view')) {
|
||||
// Add to modules menu.
|
||||
$this('admin')->addMenuItem('modules', [
|
||||
'label' => 'Drone Deploys',
|
||||
'icon' => 'drone:icon.svg',
|
||||
'route' => '/drone/deploys',
|
||||
'active' => strpos($this['route'], '/drone/deploys') === 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
133
bootstrap.php
Normal file
133
bootstrap.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implements bootstrap functions.
|
||||
*/
|
||||
|
||||
// Include addon functions only if its an admin request.
|
||||
if (COCKPIT_ADMIN && !COCKPIT_API_REQUEST) {
|
||||
// Extend addon functions.
|
||||
$this->module('drone')->extend([
|
||||
'fetchDeploys' => function ($limit = 50) {
|
||||
$settings = $this->app->config['drone'] ?? FALSE;
|
||||
|
||||
if (!$settings || !isset($settings['url'],
|
||||
$settings['owner'],
|
||||
$settings['project'],
|
||||
$settings['environment'],
|
||||
$settings['token'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// $url = $settings['api_url'] . '/sites/' . $settings['site_id'] . '/deploys' . '?access_token=' . $settings['access_token'];
|
||||
$url = trim($settings['url'], "/") . '/api/repos/' . $settings['owner'] . '/' . $settings['project'] . '/builds';
|
||||
$branch = $settings['branch'] ? $settings['branch'] : 'master';
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $settings['token']
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$deploys = [];
|
||||
$unfiltered_deploys = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$unfiltered_deploys = json_decode($unfiltered_deploys);
|
||||
$build = $settings['build'];
|
||||
if ($unfiltered_deploys && is_array($unfiltered_deploys)) {
|
||||
foreach($unfiltered_deploys as $deploy) {
|
||||
if ($deploy->{target} == $branch && ($deploy->event == 'push' || ($deploy->event == 'promote' && $deploy->deploy_to == $settings['environment']))) {
|
||||
// find latest successful build
|
||||
if (!$build) {
|
||||
if ($deploy->event == 'push' && $deploy->status == 'success') {
|
||||
$build = $deploy->number;
|
||||
} else {
|
||||
$build = $deploy->parent;
|
||||
}
|
||||
}
|
||||
$limit--;
|
||||
if ($limit>0) {
|
||||
$deploys[] = $deploy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse dates and check if any deploy is on building status.
|
||||
$building = false;
|
||||
foreach ($deploys as $idx => $deploy) {
|
||||
$deploys[$idx]->building = false;
|
||||
if (!in_array($deploy->status, ['success', 'failure'])) {
|
||||
$building = true;
|
||||
$deploys[$idx]->building = true;
|
||||
}
|
||||
$deploys[$idx]->created_at = date('Y-m-d H:i', $deploy->created);
|
||||
$deploys[$idx]->updated_at = date('Y-m-d H:i', $deploy->updated);
|
||||
if ($deploy->finished) {
|
||||
$deploys[$idx]->deploy_time = $deploy->finished - $deploy->started;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'deploys' => $deploys,
|
||||
'building' => $building,
|
||||
'build' => $build
|
||||
];
|
||||
},
|
||||
|
||||
'createDeploy' => function ($fromBuild) {
|
||||
$settings = $this->app->config['drone'];
|
||||
|
||||
if (!$fromBuild || !$settings || !isset($settings['url'],
|
||||
$settings['owner'],
|
||||
$settings['project'],
|
||||
$settings['environment'],
|
||||
$settings['token'])) {
|
||||
|
||||
return array(
|
||||
"error" => "missing settings"
|
||||
);
|
||||
}
|
||||
|
||||
$url = trim($settings['url'], "/") . '/api/repos/' . $settings['owner'] . '/' . $settings['project'] . '/builds/' . $fromBuild . '/promote?target=' . $settings['environment'];
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $settings['token']
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$result = curl_exec($ch);
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$decoded = json_decode($result);
|
||||
|
||||
if ($httpcode >= 400) {
|
||||
$decoded->error = "ERROR: $httpcode";
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
},
|
||||
]);
|
||||
|
||||
// Include admin.
|
||||
include_once __DIR__ . '/admin.php';
|
||||
}
|
8
icon.svg
Normal file
8
icon.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256px" height="218px" viewBox="0 0 256 218" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g fill="#252728">
|
||||
<path d="M128.224307,0.72249586 C32.0994301,0.72249586 0.394430682,84.5663333 0.394430682,115.221578 L78.3225537,115.221578 C78.3225537,115.221578 89.3644231,75.2760497 128.224307,75.2760497 C167.08419,75.2760497 178.130047,115.221578 178.130047,115.221578 L255.605569,115.221578 C255.605569,84.5623457 224.348186,0.72249586 128.224307,0.72249586"></path>
|
||||
<path d="M227.043854,135.175898 L178.130047,135.175898 C178.130047,135.175898 169.579477,175.122423 128.224307,175.122423 C86.8691361,175.122423 78.3225537,135.175898 78.3225537,135.175898 L30.2571247,135.175898 C30.2571247,145.426215 67.9845088,217.884246 128.699837,217.884246 C189.414168,217.884246 227.043854,158.280482 227.043854,135.175898"></path>
|
||||
<circle cx="128" cy="126.076531" r="32.7678394"></circle>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
175
views/deploys/index.php
Normal file
175
views/deploys/index.php
Normal file
@ -0,0 +1,175 @@
|
||||
<style>
|
||||
.uk-modal-details .uk-modal-dialog {
|
||||
height: 85%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<ul class="uk-breadcrumb">
|
||||
<li class="uk-active"><span>@lang('Drone Deploys')</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-top" riot-view>
|
||||
|
||||
@if($app->module('cockpit')->hasaccess('drone', 'manage.view'))
|
||||
<div class="uk-form uk-clearfix" show="{!loading}">
|
||||
@if($app->module('cockpit')->hasaccess('drone', 'manage.deploy'))
|
||||
<div class="uk-float-right">
|
||||
<a class="uk-button uk-button-primary uk-button-large" onclick="{createDeploy}">
|
||||
<i class="uk-icon-plus uk-icon-justify"></i> @lang('Deploy')
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="uk-text-xlarge uk-text-center uk-text-primary uk-margin-large-top" show="{ loading }">
|
||||
<i class="uk-icon-spinner uk-icon-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="uk-text-large uk-text-center uk-margin-large-top uk-text-muted" show="{ !loading && !deploys.length }">
|
||||
<img class="uk-svg-adjust" src="@url('drone:icon.svg')" width="100" height="100" alt="@lang('Drone Deploys')" data-uk-svg />
|
||||
<p>@lang('No deploys found')</p>
|
||||
</div>
|
||||
|
||||
<div class="uk-modal uk-modal-details uk-height-viewport">
|
||||
<div class="uk-modal-dialog uk-modal-dialog-large">
|
||||
<a href="" class="uk-modal-close uk-close"></a>
|
||||
<h3>{ deploy && deploy.title }</h3>
|
||||
<div class="uk-margin uk-flex uk-flex-middle" if="{deploy}">
|
||||
<codemirror ref="codemirror" syntax="json"></codemirror>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-form uk-clearfix" show="{!loading}">
|
||||
<table class="uk-table uk-table-tabbed uk-table-striped uk-margin-top" if="{ !loading && deploys.length }">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="uk-text-small uk-link-muted uk-noselect" width="70">
|
||||
@lang('State')
|
||||
</th>
|
||||
<th class="uk-text-small uk-link-muted uk-noselect" width="50">
|
||||
@lang('Build') #
|
||||
</th>
|
||||
<th class="uk-text-small uk-link-muted uk-noselect" width="450">
|
||||
@lang('Title')
|
||||
</th>
|
||||
<th class="uk-text-small uk-link-muted uk-noselect" width="120">
|
||||
@lang('Created')
|
||||
</th>
|
||||
<th class="uk-text-small uk-link-muted uk-noselect" width="120">
|
||||
@lang('Updated')
|
||||
</th>
|
||||
<th class="uk-text-small uk-link-muted uk-noselect" width="90">
|
||||
@lang('Deploy time')
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr each="{deploy, $index in deploys}" class="{ deploy.state == 'error' ? 'uk-text-danger' : ''}">
|
||||
<td>
|
||||
<a onclick="{ showdeployDetails }" class="extrafields-indicator uk-text-nowrap">
|
||||
<span class="uk-badge uk-text-small" if="{!deploy.building && deploy.status !== 'failure' && deploy.status !== 'success' }"><i class="uk-icon-eye uk-icon-justify"></i>{ App.i18n.get(deploy.status) }</span>
|
||||
<span class="uk-badge uk-text-small uk-badge-success" if="{deploy.status === 'success'}"><i class="uk-icon-eye uk-icon-justify"></i>{ App.i18n.get(deploy.status) }</span>
|
||||
<span class="uk-badge uk-text-small uk-badge-danger" if="{deploy.status === 'failure'}"><i class="uk-icon-eye uk-icon-justify"></i>{ App.i18n.get(deploy.status) }</span>
|
||||
<span class="uk-badge uk-text-small uk-badge-warning" if="{deploy.building}"><i class="uk-icon-spinner uk-icon-spin"></i>{ App.i18n.get(deploy.status) }</span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{ deploy.number }</td>
|
||||
<td>{ deploy.event }: { deploy.message }</td>
|
||||
<td><span class="uk-badge uk-badge-outline uk-text-muted">{ deploy.created_at }</span></td>
|
||||
<td><span class="uk-badge uk-badge-outline uk-text-muted">{ deploy.updated_at }</span></td>
|
||||
<td><span if="{deploy.deploy_time}">{ deploy.deploy_time }s</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<script type="view/script">
|
||||
|
||||
var $this = this;
|
||||
$this.deploy = {};
|
||||
$this.loading = true;
|
||||
$this.deploys = {{ json_encode($deploys) }};
|
||||
$this.building = {{ json_encode($building) }};
|
||||
$this.build = {{ json_encode($build) }};
|
||||
|
||||
this.on('mount', function() {
|
||||
$this.loading = false;
|
||||
$this.modal = UIkit.modal(App.$('.uk-modal-details', this.root), {modal:true});
|
||||
if ($this.building) {
|
||||
setTimeout(function() {
|
||||
$this.fetchData();
|
||||
}, 5000);
|
||||
}
|
||||
$this.update();
|
||||
});
|
||||
|
||||
showdeployDetails(e) {
|
||||
$this.deploy = e.item.deploy;
|
||||
$this.modal.show();
|
||||
editor = $this.refs.codemirror.editor;
|
||||
editor.setValue(JSON.stringify($this.deploy, null, 2), true);
|
||||
editor.setOption("readOnly", true);
|
||||
editor.setSize($this.modal.dialog[0].clientWidth - 50, $this.modal.dialog[0].clientHeight - 70);
|
||||
editor.refresh();
|
||||
$this.trigger('ready');
|
||||
}
|
||||
|
||||
createDeploy() {
|
||||
if ($this.building) {
|
||||
App.ui.notify(App.i18n.get("A deploy is already in progress, please wait until finishes."), "warning");
|
||||
} else {
|
||||
UIkit.modal.confirm(App.i18n.get("Triggering a new Drone deploy. Are you sure?<br><br>Using build: #") + $this.build, function() {
|
||||
App.callmodule('drone:createDeploy', $this.build).then(function(data) {
|
||||
if (!data || !data.result || data.result.error) {
|
||||
if (data && data.result && data.result.error) {
|
||||
App.ui.notify(App.i18n.get(data.result.error), "danger");
|
||||
} else {
|
||||
App.ui.notify("unknown error", "danger");
|
||||
}
|
||||
if (data) {
|
||||
$this.modal.show();
|
||||
editor = $this.refs.codemirror.editor;
|
||||
editor.setValue(JSON.stringify(data, null, 2), true);
|
||||
editor.setOption("readOnly", true);
|
||||
editor.setSize($this.modal.dialog[0].clientWidth - 50, $this.modal.dialog[0].clientHeight - 70);
|
||||
editor.refresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
App.ui.notify(App.i18n.get("A new deploy was requested."), "success");
|
||||
setTimeout(function() {
|
||||
App.ui.notify(App.i18n.get("Fetching deploy status..."), "success");
|
||||
$this.building = true;
|
||||
$this.fetchData();
|
||||
}, 2000)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchData() {
|
||||
if (!this.building) {
|
||||
return;
|
||||
}
|
||||
App.callmodule('drone:fetchDeploys').then(function(data) {
|
||||
if (data && data.result && data.result.deploys) {
|
||||
$this.deploys = data.result.deploys;
|
||||
$this.building = data.result.building;
|
||||
$this.build = data.result.build;
|
||||
setTimeout(function() {
|
||||
$this.fetchData();
|
||||
}, 5000);
|
||||
} else {
|
||||
App.ui.notify(App.i18n.get("Cannot fetch deploys from Drone! Try again later."), "danger");
|
||||
}
|
||||
$this.update();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user