Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Vercel #64

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ For example, if we were wanting to use Node on Lambda to generate an og:image fo
```php
namespace App\Sidecar;

use Hammerstone\Sidecar\LambdaFunction;
use Hammerstone\Sidecar\ServerlessFunction;

class OgImage extends LambdaFunction
class OgImage extends ServerlessFunction
{
public function handler()
{
Expand Down
34 changes: 34 additions & 0 deletions config/sidecar.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,38 @@
* See CreateExecutionRole::policy for the IAM policy.
*/
'execution_role' => env('SIDECAR_EXECUTION_ROLE'),

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* If you're not using Vercel, you can skip this whole section. You can *
* edit this directly or use `php artisan sidecar:configure --vercel`. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
* Your Vercel API token. If you're not using Vercel to deploy
* your functions, you won't need this. You can generate
* a token at https://vercel.com/account/tokens.
*/
'vercel_token' => env('SIDECAR_VERCEL_TOKEN'),

/*
* This is a random string used to generate unique, determinative
* domain names on Vercel. It is not used for security in any
* way. It should be 16 random alphanumeric characters.
*/
'vercel_domain_seed' => env('SIDECAR_VERCEL_DOMAIN_SEED'),

/*
* This is the secret token that Sidecar uses to sign outgoing function
* invocations. The same secret will be used to validate the requests
* on Vercel. You need to redeploy your functions if this changes!
*/
'vercel_signing_secret' => env('SIDECAR_VERCEL_SIGNING_SECRET'),

/*
* If you are a part of a team, you can deploy your functions into
* that team specifically by supplying the team ID here.
*/
'vercel_team' => env('SIDECAR_VERCEL_TEAM'),
];
10 changes: 5 additions & 5 deletions docs/functions/handlers-and-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Every Lambda function requires at least two things:
- the name of handler function
- the file or files needed to execute that handler function

Because these two things are required, these are the two abstract methods that you must implement in your Sidecar function class.
Because these two things are required, these are the two abstract methods that you must implement in your Sidecar function class.

```php
use Hammerstone\Sidecar\LambdaFunction;
use Hammerstone\Sidecar\ServerlessFunction;

class ExampleFunction extends LambdaFunction
class ExampleFunction extends ServerlessFunction
{
public function handler()
{
Expand Down Expand Up @@ -407,10 +407,10 @@ To use a container image with Sidecar you must first build a Lambda compatible d
Once the container has been added to the registry update the Sidecar function's handler method to return the `Package::CONTAINER_HANDLER` constant. Finally, update the function's `package` method to return the ECR Image URI as shown below.

```php
use Hammerstone\Sidecar\LambdaFunction;
use Hammerstone\Sidecar\ServerlessFunction;
use Hammerstone\Sidecar\Package;

class ExampleFunction extends LambdaFunction
class ExampleFunction extends ServerlessFunction
{
public function handler()
{
Expand Down
5 changes: 3 additions & 2 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ Every Sidecar Function requires two things:
For example, if you want to use Node on Lambda to generate an og:image for all of your blog posts, you would first set up a simple class in PHP called e.g. `OgImage`.

App\Sidecar\OgImage.php {.filename}

```php
namespace App\Sidecar;

use Hammerstone\Sidecar\LambdaFunction;
use Hammerstone\Sidecar\ServerlessFunction;

class OgImage extends LambdaFunction
class OgImage extends ServerlessFunction
{
public function handler()
{
Expand Down
65 changes: 36 additions & 29 deletions src/Clients/LambdaClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,31 @@
use Aws\Lambda\LambdaClient as BaseClient;
use Aws\Result;
use Exception;
use Hammerstone\Sidecar\LambdaFunction;
use Hammerstone\Sidecar\Contracts\FaasClient;
use Hammerstone\Sidecar\ServerlessFunction;
use Hammerstone\Sidecar\Sidecar;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class LambdaClient extends BaseClient
class LambdaClient extends BaseClient implements FaasClient
{
const CREATED = 1;
const UPDATED = 2;
const NOOP = 3;

/**
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @return string
*/
public function getLatestVersion(LambdaFunction $function)
public function getLatestVersion(ServerlessFunction $function)
{
return last($this->getVersions($function))['Version'];
}

/**
* Test whether the latest deployed version is the one that is aliased.
*
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @param $alias
* @return bool
*/
public function latestVersionHasAlias(LambdaFunction $function, $alias)
public function latestVersionHasAlias(ServerlessFunction $function, $alias)
{
$version = $this->getLatestVersion($function);

Expand All @@ -46,11 +43,11 @@ public function latestVersionHasAlias(LambdaFunction $function, $alias)
}

/**
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @param null|string $marker
* @return \Aws\Result
*/
public function getVersions(LambdaFunction $function, $marker = null)
public function getVersions(ServerlessFunction $function, $marker = null)
{
$result = $this->listVersionsByFunction([
'FunctionName' => $function->nameWithPrefix(),
Expand All @@ -68,20 +65,20 @@ public function getVersions(LambdaFunction $function, $marker = null)
}

/**
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @param string $alias
* @param string|null $version
* @return int
*/
public function aliasVersion(LambdaFunction $function, $alias, $version = null)
public function aliasVersion(ServerlessFunction $function, $alias, $version = null)
{
$version = $version ?? $this->getLatestVersion($function);

$aliased = $this->getAliasWithoutException($function, $alias);

// The alias already exists and it's the version we were trying to alias anyway.
if ($aliased && $version === Arr::get($aliased, 'FunctionVersion')) {
return self::NOOP;
return FaasClient::NOOP;
}

$args = [
Expand All @@ -93,20 +90,20 @@ public function aliasVersion(LambdaFunction $function, $alias, $version = null)
if ($aliased) {
$this->updateAlias($args);

return self::UPDATED;
return FaasClient::UPDATED;
}

$this->createAlias($args);

return self::CREATED;
return FaasClient::CREATED;
}

/**
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @param $name
* @return \Aws\Result|false
*/
public function getAliasWithoutException(LambdaFunction $function, $name)
public function getAliasWithoutException(ServerlessFunction $function, $name)
{
try {
return $this->getAlias([
Expand All @@ -123,12 +120,22 @@ public function getAliasWithoutException(LambdaFunction $function, $name)
}

/**
* @param LambdaFunction $function
* @param ServerlessFunction $function
*
* @throws \Hammerstone\Sidecar\Exceptions\SidecarException
*/
public function createNewFunction(ServerlessFunction $function)
{
$this->createFunction($function->toDeploymentArray());
}

/**
* @param ServerlessFunction $function
* @return int
*
* @throws Exception
*/
public function updateExistingFunction(LambdaFunction $function)
public function updateExistingFunction(ServerlessFunction $function)
{
$config = $function->toDeploymentArray();

Expand All @@ -138,7 +145,7 @@ public function updateExistingFunction(LambdaFunction $function)

// See if the function already exists with these *exact* parameters.
if ($this->functionExists($function, $checksum)) {
return self::NOOP;
return FaasClient::NOOP;
}

// Add the checksum to the description, so we can look for it next time.
Expand Down Expand Up @@ -167,7 +174,7 @@ public function updateExistingFunction(LambdaFunction $function)
$this->updateFunctionCode($code);
}

public function updateFunctionVariables(LambdaFunction $function)
public function updateFunctionVariables(ServerlessFunction $function)
{
$variables = $function->variables();

Expand Down Expand Up @@ -219,9 +226,9 @@ public function updateFunctionVariables(LambdaFunction $function)
* @link https://github.com/hammerstonedev/sidecar/issues/32
* @link https://github.com/aws/aws-sdk-php/blob/master/src/data/lambda/2015-03-31/waiters-2.json
*
* @param LambdaFunction $function
* @param ServerlessFunction $function
*/
public function waitUntilFunctionUpdated(LambdaFunction $function)
public function waitUntilFunctionUpdated(ServerlessFunction $function)
{
$this->waitUntil('FunctionUpdated', [
'FunctionName' => $function->nameWithPrefix(),
Expand All @@ -231,10 +238,10 @@ public function waitUntilFunctionUpdated(LambdaFunction $function)
/**
* Delete a particular version of a function.
*
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @param string $version
*/
public function deleteFunctionVersion(LambdaFunction $function, $version)
public function deleteFunctionVersion(ServerlessFunction $function, $version)
{
$this->deleteFunction([
'FunctionName' => $function->nameWithPrefix(),
Expand All @@ -243,11 +250,11 @@ public function deleteFunctionVersion(LambdaFunction $function, $version)
}

/**
* @param LambdaFunction $function
* @param ServerlessFunction $function
* @param null $checksum
* @return bool
*/
public function functionExists(LambdaFunction $function, $checksum = null)
public function functionExists(ServerlessFunction $function, $checksum = null)
{
try {
$response = $this->getFunction([
Expand Down
69 changes: 69 additions & 0 deletions src/Commands/Configurators/ConfigureVercel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
/**
* @author Aaron Francis <[email protected]|https://twitter.com/aarondfrancis>
*/

namespace Hammerstone\Sidecar\Commands\Configurators;

use Hammerstone\Sidecar\Vercel\Client;
use Illuminate\Support\Str;

trait ConfigureVercel
{
public function configureVercel()
{
$this->line(str_repeat('-', $this->width));
$this->text('This interactive command will set up your Sidecar credentials for Vercel.');
$this->line('');
$this->text("The first thing you'll need is a Vercel token, which you can generate here:");
$this->text('https://vercel.com/account/tokens');
$this->line(str_repeat('-', $this->width));
$this->line('');

$token = $this->secret('Paste your Vercel token, or press enter to skip');
$team = $token ? $this->selectTeam($token) : '';

$this->line(' ');
$this->info('Done! Here are your environment variables:');
$this->line('SIDECAR_VERCEL_TOKEN=' . $token);
$this->line('SIDECAR_VERCEL_TEAM=' . $team);
$this->line('SIDECAR_VERCEL_DOMAIN_SEED=' . Str::random(16));
$this->line('SIDECAR_VERCEL_SIGNING_SECRET=' . Str::random(40));
$this->line(' ');
$this->info('They will work in any environment.');
}

protected function selectTeam($token)
{
$vercel = new Client([
'base_uri' => 'https://api.vercel.com',
'allow_redirects' => true,
'headers' => [
'Authorization' => 'Bearer ' . $token,
]
]);

$teams = collect($vercel->listTeams()['teams'])->mapWithKeys(function ($team) {
return [$team['id'] => $team['name']];
});

if (!count($teams)) {
return;
}

$teams['personal'] = 'Personal Account';

$team = $this->choice(
'You are a part of one or more teams. Where would you like your functions deployed?',
$teams->values()->toArray()
);

$id = $teams->flip()[$team];

if ($id === 'personal') {
return;
}

return $id;
}
}
13 changes: 11 additions & 2 deletions src/Commands/Configure.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,27 @@
use Hammerstone\Sidecar\Commands\Actions\CreateExecutionRole;
use Hammerstone\Sidecar\Commands\Actions\DestroyAdminKeys;
use Hammerstone\Sidecar\Commands\Actions\DetermineRegion;
use Hammerstone\Sidecar\Commands\Configurators\ConfigureVercel;
use Illuminate\Console\Command;
use Throwable;

class Configure extends Command
{
use ConfigureVercel;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sidecar:configure';
protected $signature = 'sidecar:configure {--vercel}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Interactively configure your Sidecar AWS environment variables.';
protected $description = 'Interactively configure your Sidecar environment variables.';

/**
* @var string
Expand Down Expand Up @@ -56,6 +59,12 @@ class Configure extends Command
*/
public function handle()
{
if ($this->option('vercel')) {
return $this->configureVercel();
}

// @TODO Factor AWS configuration out to a trait.

$this->askForAdminCredentials();

$this->region = $this->action(DetermineRegion::class)->invoke();
Expand Down
Loading