Skip to content

Commit

Permalink
Merge pull request #26 from ronanchilvers/bitbucket
Browse files Browse the repository at this point in the history
Implement bitbucket support for deployments
  • Loading branch information
ronanchilvers authored Nov 27, 2019
2 parents 4f75272 + 05c35fd commit 72ebe27
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 34 deletions.
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,40 @@ In addition it is *strongly* recommended that you use a proper RDBMS like MySQL,

Once you have the required software installed on the host you can then get on with the installation.

### Generating Provider Tokens

`deploy` currently supports the three main VCS providers - Github, Gitlab and Bitbucket. Not all have to be configured for `deploy` to work but you will need at least one!

**NB:** In the examples below we add each configuration variable to the `providers` section. Just in case its not clear, you should only have a single `providers` section and each set of credentials should be added below it. See the `local.yaml.dist` file as a reference.

#### Github Personal Access Token

To generate a personal access token, navigate to [your Personal access tokens settings page](https://github.com/settings/tokens). Generate a token with the `repo` scope enabled - no others are needed. Add the token to your `deploy` configuration in the `providers` section like this:
```yaml
providers:
github:
token: thisismysuperlongtokensecret
```
#### Gitlab Personal Access Token
Visit the [Personal Access Tokens page](https://gitlab.com/profile/personal_access_tokens) in your Gitlab account and generate a token with `api` scope. You can add an expiry date if you want to but don't forget to replace it when the time comes!! (I'd recommend not setting an expiry or setting a very long one). Then add your token to the `providers` section like this:
```yaml
providers:
gitlab:
token: fancygitlabtokengoeshere
```

#### Bitbucket App Password

For Bitbucket support `deploy` currently uses an app password. This may change in future though. At the moment you can generate one by visiting Bitbucket Settings > Access Management | App Passwords. Once in there generate a new token with `Repositories > Read` scope. Then add your username and app password to your `deploy` configuration like this:
```yaml
providers:
bitbucket:
username: myusername
token: shineyapppasswordhere
```

### Codebase setup

* Create a database and database user in your chosen DBMS. `deploy` needs CREATE, DROP, ALTER, SELECT, INSERT, UPDATE, DELETE, INDEX permissions. For MariaDB / MySQL it's likely to be something like this:
Expand All @@ -49,7 +83,7 @@ cd deploy
composer install
```

* Create the local configuration. Instructions are provided within the file.
* Create the local configuration. Instructions are provided within the file. See above for some guidance on generating tokens to use with the various VCS providers.
```bash
cp local.yaml.dist local.yaml
```
Expand Down Expand Up @@ -100,10 +134,10 @@ notify:
```
- `composer` - This directive allows you to control the behaviour of the composer dependency manager, assuming that it is used in your project. If `deploy` doesn't find a `composer.json` file in the root of your working copy, composer support is disabled and this directive has no effect.
- `install` - Define the command composer will install dependencies with. The default is `install --no-interaction --prefer-dist --no-dev --optimize-autoloader`
- `command` - Define the command composer will install dependencies with. The default is `install --no-interaction --prefer-dist --no-dev --optimize-autoloader`
```yaml
composer:
install: install --no-dev -o
command: install --no-dev -o
```

- `shared` - Define shared folders or files. These are locations that persist between deployments, for example a cache directory or configuration file. The `files` and `folders` subkeys can be used to define a list or files or folders that should be shared. Paths are always relative to the root of the deployment working copy.
Expand Down Expand Up @@ -242,3 +276,7 @@ cleanup:
* https://developer.github.com/v3/repos/contents/#get-contents
* https://mattstauffer.com/blog/introducing-envoyer.io/
* https://docs.gitlab.com/ee/api/repositories.html#get-file-archive
* https://stackoverflow.com/questions/35160169/bitbucket-how-to-download-latest-tar-gz-file-of-a-bitbucket-repo-programmatical
* https://stackoverflow.com/questions/17682143/download-private-bitbucket-repository-zip-file-using-http-authentication
* https://community.atlassian.com/t5/Bitbucket-questions/How-to-download-repository-as-zip-file-using-the-API/qaq-p/862113
* https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html
3 changes: 3 additions & 0 deletions local.yaml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ providers:
token: <github_personal_access_token>
gitlab:
token: <gitlab_personal_access_token>
bitbucket:
username: <bitbucket_username>
token: <bitbucket_app_password>
2 changes: 1 addition & 1 deletion resources/templates/project/view.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
App.Deploy.init({
project: '{{ project.key }}'
});
{% if not selected_deployment.deployed and not selected_deployment.failed %}
{% if selected_deployment and not selected_deployment.deployed and not selected_deployment.failed %}
App.Monitor.init({
project: '{{ project.key }}',
number: {{ selected_deployment.number }}
Expand Down
44 changes: 32 additions & 12 deletions src/Provider/AbstractProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,23 +237,19 @@ protected function processRefArray(array $data): array
public function scanConfiguration(Project $project, Deployment $deployment, Closure $closure = null)
{
try {
$repository = $this->encodeRepository($project->repository);
$params = [
'repository' => $repository,
'sha' => $deployment->sha,
];
$url = Str::moustaches(
$this->configUrl,
$params
$raw = $this->getConfiguration(
$project,
$deployment
);
$data = $this->getJSON($url);
$yaml = base64_decode($data['content']);
$yaml = Yaml::parse($yaml);
$yaml = Yaml::parse($raw);
if (is_null($yaml)) {
$yaml = [];
}
$closure(
'info',
implode("\n", [
'YAML deployment configuration read successfully',
"JSON: " . json_encode($data, JSON_PRETTY_PRINT)
"YAML: " . $raw
])
);

Expand Down Expand Up @@ -289,6 +285,30 @@ public function scanConfiguration(Project $project, Deployment $deployment, Clos
}
}

/**
* Try to download the deploy.yaml file from the remote repository
*
* @param \App\Model\Project $project
* @param \App\Model\Deployment $deployment
* @author Ronan Chilvers <ronan@d3r.com>
*/
protected function getConfiguration(Project $project, Deployment $deployment)
{
$repository = $this->encodeRepository($project->repository);
$params = [
'repository' => $repository,
'sha' => $deployment->sha,
];
$url = Str::moustaches(
$this->configUrl,
$params
);
$data = $this->getJSON($url);
$yaml = base64_decode($data['content']);

return $yaml;
}

/**
* @see \App\Provider\ProviderInterface::download()
*/
Expand Down
141 changes: 141 additions & 0 deletions src/Provider/Bitbucket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

namespace App\Provider;

use App\Builder;
use App\Facades\Log;
use App\Facades\Settings;
use App\Model\Deployment;
use App\Model\Project;
use App\Provider\AbstractProvider;
use App\Provider\ProviderInterface;
use Closure;
use Exception;
use Ronanchilvers\Foundation\Config;
use Ronanchilvers\Utility\Str;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;

/**
* Bitbucket source control provider
*
* @author Ronan Chilvers <ronan@d3r.com>
*/
class Bitbucket extends AbstractProvider implements ProviderInterface
{
/**
* @var array
*/
protected $typesHandled = ['bitbucket'];

/**
* @var string
*/
protected $headUrl = 'https://api.bitbucket.org/2.0/repositories/{repository}/commits/{branch}?pagelen=1';

/**
* @var string
*/
protected $branchesUrl = 'https://api.bitbucket.org/2.0/repositories/{repository}/refs/branches?pagelen=50';

/**
* @var string
*/
protected $tagsUrl = 'https://api.bitbucket.org/2.0/repositories/{repository}/refs/tags?pagelen=50';

/**
* @var string
*/
protected $downloadUrl = 'https://bitbucket.org/{repository}/get/{sha}.zip';

/**
* @var string
*/
protected $configUrl = 'https://api.bitbucket.org/2.0/repositories/{repository}/src/{sha}/deploy.yaml';

/**
* @var string
*/
protected $repoUrl = 'https://bitbucket.org/{repository}';

/**
* @var string
*/
protected $branchUrl = 'https://bitbucket.org/{repository}/src/{branch}';

/**
* @var string
*/
protected $shaUrl = 'https://bitbucket.org/{repository}/commits/{sha}';

/**
* @see \App\Provider\ProviderInterface::getHeadInfo()
*/
public function getHeadInfo(string $repository, string $type, string $ref)
{
$params = [
'repository' => $this->encodeRepository($repository),
'branch' => $ref,
];
$url = Str::moustaches(
$this->headUrl,
$params
);
$data = $this->getJSON($url);
if (!is_array($data) || !isset($data['values'], $data['values'][0])) {
throw new RuntimeException('No data found for head commit');
}

return [
'sha' => $data['values'][0]['hash'],
'author' => $data['values'][0]['author']['raw'],
'committer' => $data['values'][0]['author']['raw'],
'message' => $data['values'][0]['summary']['raw'],
];
}

/**
* Try to download the deploy.yaml file from the remote repository
*
* @param \App\Model\Project $project
* @param \App\Model\Deployment $deployment
* @author Ronan Chilvers <ronan@d3r.com>
*/
protected function getConfiguration(Project $project, Deployment $deployment)
{
$repository = $this->encodeRepository($project->repository);
$params = [
'repository' => $repository,
'sha' => $deployment->sha,
];
$url = Str::moustaches(
$this->configUrl,
$params
);
$response = $this->get($url);

return $response->getBody()->getContents();
}

/**
* Process a ref arrays into simplified form
*
* @param array $data
* @return array
* @author Ronan Chilvers <ronan@d3r.com>
*/
protected function processRefArray(array $data): array
{
if (!isset($data['values']) || 0 == count($data['values'])) {
return [];
}
$output = [];
foreach ($data['values'] as $datum) {
$output[$datum['name']] = $datum['name'];
}

return $output;
}
}
5 changes: 0 additions & 5 deletions src/Provider/Gitlab.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ class Gitlab extends AbstractProvider implements ProviderInterface
*/
protected $typesHandled = ['gitlab'];

/**
* @var string
*/
protected $token;

/**
* @var string
*/
Expand Down
22 changes: 9 additions & 13 deletions src/Provider/ProviderProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Provider;

use App\Facades\Settings;
use App\Provider\Bitbucket;
use App\Provider\Github;
use App\Provider\Gitlab;
use GuzzleHttp\Client;
Expand All @@ -24,19 +25,6 @@ class ProviderProvider implements ServiceProviderInterface
*/
public function register(Container $container)
{
// Github
// $container->share(Github::class, function($c) {
// $token = Settings::get('providers.github.token');

// return new Github($token);
// });
// Gitlab
// $container->share(Gitlab::class, function($c) {
// $token = Settings::get('providers.gitlab.token');

// return new Gitlab($token);
// });

$container->share(Factory::class, function($c) {
$factory = new Factory();

Expand All @@ -58,6 +46,14 @@ public function register(Container $container)
$factory->addProvider(new Gitlab($client, $token));
}

if ($token = Settings::get('providers.bitbucket.token', false)) {
$username = Settings::get('providers.bitbucket.username', false);
$client = new Client([
'auth' => [ $username, $token ],
]);
$factory->addProvider(new Bitbucket($client, $token));
}

return $factory;
});
}
Expand Down

0 comments on commit 72ebe27

Please sign in to comment.