Compare commits
5 Commits
9e5adfb9ce
...
3fa0e7920e
Author | SHA1 | Date |
---|---|---|
Adam Pippin | 3fa0e7920e | 3 years ago |
Adam Pippin | 949a8deb8f | 3 years ago |
Adam Pippin | 0737fcd74a | 3 years ago |
Adam Pippin | 2082f6238a | 3 years ago |
Adam Pippin | 701b25779e | 3 years ago |
22 changed files with 823 additions and 32 deletions
@ -0,0 +1,75 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression\Token; |
||||
|
|
||||
|
use App\Cfnpp\Expression\Token; |
||||
|
use App\Cfnpp\Expression\TokenLiteral; |
||||
|
|
||||
|
/** |
||||
|
* A boolean literal (true/false). |
||||
|
* |
||||
|
* @author Adam Pippin <hello@adampippin.ca> |
||||
|
*/ |
||||
|
class BooleanLiteral extends TokenLiteral |
||||
|
{ |
||||
|
/** |
||||
|
* Value of this literal. |
||||
|
* @var bool |
||||
|
*/ |
||||
|
protected $value; |
||||
|
|
||||
|
/** |
||||
|
* New boolean literal. |
||||
|
* |
||||
|
* @param bool $value |
||||
|
*/ |
||||
|
public function __construct(bool $value) |
||||
|
{ |
||||
|
$this->value = $value; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the value of this literal. |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function getValue(): bool |
||||
|
{ |
||||
|
return $this->value; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
return |
||||
|
(strlen($stream) >= 4 && strtolower(substr($stream, 0, 4)) == 'true') || |
||||
|
(strlen($stream) >= 5 && strtolower(substr($stream, 0, 5)) == 'false'); |
||||
|
} |
||||
|
|
||||
|
public static function getToken(string &$stream): Token |
||||
|
{ |
||||
|
if (strlen($stream) >= 4 && strtolower(substr($stream, 0, 4)) == 'true') |
||||
|
{ |
||||
|
$stream = substr($stream, 4); |
||||
|
return new BooleanLiteral(true); |
||||
|
} |
||||
|
elseif (strlen($stream) >= 5 && strtolower(substr($stream, 0, 5)) == 'false') |
||||
|
{ |
||||
|
$stream = substr($stream, 5); |
||||
|
return new BooleanLiteral(false); |
||||
|
} |
||||
|
throw new \Exception('Unparseable boolean'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the value of this token. |
||||
|
* |
||||
|
* @param ?\App\Util\GraphNode[] $arguments |
||||
|
* @return \App\Util\GraphNode|Token|scalar|null |
||||
|
*/ |
||||
|
public function execute(?array $arguments = null) |
||||
|
{ |
||||
|
return $this->getValue(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\CloudFormation; |
||||
|
|
||||
|
use Aws\CloudFormation\CloudFormationClient; |
||||
|
|
||||
|
class Client |
||||
|
{ |
||||
|
protected $cfn; |
||||
|
|
||||
|
public function __construct(?string $region = null, ?string $profile = null) |
||||
|
{ |
||||
|
$this->cfn = new CloudFormationClient([ |
||||
|
'version' => 'latest', |
||||
|
'region' => $region ?? env('AWS_REGION'), |
||||
|
'profile' => $profile ?? env('AWS_PROFILE') |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
public function getStack(string $stack): ?Stack |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
$stack_description = $this->cfn->describeStacks([ |
||||
|
'StackName' => $stack |
||||
|
]); |
||||
|
} |
||||
|
catch (\Aws\Exception\AwsException $ex) |
||||
|
{ |
||||
|
if ($ex->getAwsErrorMessage() == 'Stack with id '.$stack.' does not exist') |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
throw new \Exception($ex->getAwsErrorMessage(), $ex->getAwsErrorCode()); |
||||
|
} |
||||
|
return new Stack($stack_description['Stacks'][0]); |
||||
|
} |
||||
|
|
||||
|
public function createStack(string $stack_name, string $template_body, array $parameters = []): Stack |
||||
|
{ |
||||
|
$response = $this->cfn->createStack($this->buildStackRequest($stack_name, $template_body, $parameters)); |
||||
|
return $this->getStack($response['StackId']); |
||||
|
} |
||||
|
|
||||
|
public function updateStack(string $stack_name, string $template_body, array $parameters = []): Stack |
||||
|
{ |
||||
|
$response = $this->cfn->updateStack($this->buildStackRequest($stack_name, $template_body, $parameters)); |
||||
|
return $this->getStack($response['StackId']); |
||||
|
} |
||||
|
|
||||
|
protected function buildStackRequest(string $stack_name, string $template_body, array $parameters = []): array |
||||
|
{ |
||||
|
$request = [ |
||||
|
'StackName' => $stack_name, |
||||
|
'TemplateBody' => $template_body, |
||||
|
'Capabilities' => ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], |
||||
|
'TimeoutInMinutes' => 5 |
||||
|
]; |
||||
|
if (sizeof($parameters)) |
||||
|
{ |
||||
|
$request['Parameters'] = []; |
||||
|
foreach ($parameters as $k => $v) |
||||
|
{ |
||||
|
$request['Parameters'][] = [ |
||||
|
'ParameterKey' => $k, |
||||
|
'ParameterValue' => $v |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
|
return $request; |
||||
|
} |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\CloudFormation; |
||||
|
|
||||
|
class Stack |
||||
|
{ |
||||
|
protected $stack_data; |
||||
|
|
||||
|
public function __construct(array $data) |
||||
|
{ |
||||
|
$this->stack_data = $data; |
||||
|
} |
||||
|
|
||||
|
public function getId(): string |
||||
|
{ |
||||
|
return $this->stack_data['StackId']; |
||||
|
} |
||||
|
|
||||
|
public function getName(): string |
||||
|
{ |
||||
|
return $this->stack_data['StackName']; |
||||
|
} |
||||
|
|
||||
|
public function getStatus(): string |
||||
|
{ |
||||
|
return $this->stack_data['StackStatus']; |
||||
|
} |
||||
|
|
||||
|
public function getStatusReason(): ?string |
||||
|
{ |
||||
|
return $this->stack_data['StackStatusReason'] ?? null; |
||||
|
} |
||||
|
|
||||
|
public function getParameters(): array |
||||
|
{ |
||||
|
if (!isset($this->stack_data['Parameters'])) |
||||
|
{ |
||||
|
return []; |
||||
|
} |
||||
|
$parameters = []; |
||||
|
foreach ($this->stack_data['Parameters'] as $parameter) |
||||
|
{ |
||||
|
$parameters[$parameter['ParameterKey']] = $parameter['ParameterValue']; |
||||
|
} |
||||
|
return $parameters; |
||||
|
} |
||||
|
|
||||
|
public function getOutputs(): array |
||||
|
{ |
||||
|
if (!isset($this->stack_data['Outputs'])) |
||||
|
{ |
||||
|
return []; |
||||
|
} |
||||
|
$outputs = []; |
||||
|
foreach ($this->stack_data['Outputs'] as $output) |
||||
|
{ |
||||
|
$outputs[$output['OutputKey']] = $output['OutputValue']; |
||||
|
} |
||||
|
return $outputs; |
||||
|
} |
||||
|
} |
@ -0,0 +1,85 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Commands\Stack; |
||||
|
|
||||
|
use LaravelZero\Framework\Commands\Command; |
||||
|
|
||||
|
/** |
||||
|
* Read a file as input and perform all necessary compilation steps before |
||||
|
* writing it to an output file. |
||||
|
* |
||||
|
* @author Adam Pippin <hello@adampippin.ca> |
||||
|
*/ |
||||
|
class Deploy extends Command |
||||
|
{ |
||||
|
/** |
||||
|
* The signature of the command. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $signature = 'stack:deploy {in_file} {--format=Yaml}'; |
||||
|
|
||||
|
/** |
||||
|
* The description of the command. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $description = 'Read an input file, process, output result to a file'; |
||||
|
|
||||
|
/** |
||||
|
* Execute the console command. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function handle() |
||||
|
{ |
||||
|
$engine = new \App\Engine\Engine(); |
||||
|
|
||||
|
$serializer = $this->getSerializer(); |
||||
|
$unserializer = $this->getUnserializer(); |
||||
|
$engine->setSerializer($serializer)->setUnserializer($unserializer)->setCompiler(new \App\Cfnpp\Compiler()); |
||||
|
$options = new \App\Cfnpp\Options(); |
||||
|
|
||||
|
$output = $engine->process($this->argument('in_file'), $options); |
||||
|
|
||||
|
file_put_contents($this->argument('out_file'), $output); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Retrieve a serializer instance based on the format specified in the command options. |
||||
|
* |
||||
|
* @return \App\Serialize\ISerialize |
||||
|
*/ |
||||
|
protected function getSerializer(): \App\Serialize\ISerialize |
||||
|
{ |
||||
|
$format = $this->option('format'); |
||||
|
$class = '\\App\\Serialize\\'.$format; |
||||
|
|
||||
|
if (!class_exists($class)) |
||||
|
{ |
||||
|
throw new \Exception('Unknown formatter: '.$format); |
||||
|
} |
||||
|
|
||||
|
return new $class(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Retrieve a unserializer instance based on the format specified in the command options. |
||||
|
* |
||||
|
* @return \App\Serialize\IUnserialize |
||||
|
*/ |
||||
|
protected function getUnserializer(): \App\Serialize\IUnserialize |
||||
|
{ |
||||
|
$format = $this->option('format'); |
||||
|
$class = '\\App\\Serialize\\'.$format; |
||||
|
|
||||
|
if (!class_exists($class)) |
||||
|
{ |
||||
|
throw new \Exception('Unknown formatter: '.$format); |
||||
|
} |
||||
|
|
||||
|
return new $class(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Commands; |
||||
|
|
||||
|
use Illuminate\Console\Scheduling\Schedule; |
||||
|
use LaravelZero\Framework\Commands\Command; |
||||
|
|
||||
|
class TrashCommand extends Command |
||||
|
{ |
||||
|
/** |
||||
|
* The signature of the command. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $signature = 'trash'; |
||||
|
|
||||
|
/** |
||||
|
* The description of the command. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $description = 'Command description'; |
||||
|
|
||||
|
/** |
||||
|
* Execute the console command. |
||||
|
* |
||||
|
* @return mixed |
||||
|
*/ |
||||
|
public function handle() |
||||
|
{ |
||||
|
$options = new \App\Cfnpp\Options(); |
||||
|
$options->setVariables(['A' => true]); |
||||
|
$options->setParameters(['LBType' => 'internal', 'Other' => true]); |
||||
|
|
||||
|
$expr = new \App\Cfnpp\Expression\Expression('"internal" Other eq', $options); |
||||
|
|
||||
|
if ($expr->isComplete()) |
||||
|
{ |
||||
|
echo 'Got value: '.PHP_EOL; |
||||
|
var_dump($expr->toArray()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
echo 'Expression not resolved: '.PHP_EOL; |
||||
|
var_dump($expr->toCloudformation()); |
||||
|
} |
||||
|
/* |
||||
|
$cfn = new \App\CloudFormation\Client(); |
||||
|
$stack = $cfn->getStack('CloudBender'); |
||||
|
if (!isset($stack)) |
||||
|
throw new \Exception("Stack not found"); |
||||
|
var_dump($stack->getId(), $stack->getName(), $stack->getStatus(), $stack->getStatusReason(), $stack->getParameters(), $stack->getOutputs()); |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Define the command's schedule. |
||||
|
* |
||||
|
* @param \Illuminate\Console\Scheduling\Schedule $schedule |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function schedule(Schedule $schedule): void |
||||
|
{ |
||||
|
// $schedule->command(static::class)->everyMinute(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
cfnpp: |
||||
|
stack: |
||||
|
- doc3.yaml |
||||
|
- doc4.yaml |
||||
|
variables: |
||||
|
A: 2 |
||||
|
B: 2 |
||||
|
|
||||
|
AwsThing: '2020-01-01' |
||||
|
|
||||
|
Resources: |
||||
|
Thing: |
||||
|
A: 1 |
||||
|
B: 2 |
||||
|
AnArr: |
||||
|
- 1 |
||||
|
- 2 |
||||
|
Thing3: !var C |
@ -0,0 +1,14 @@ |
|||||
|
cfnpp: |
||||
|
stack: |
||||
|
- doc1.yaml |
||||
|
variables: |
||||
|
A: 1 |
||||
|
parameters: |
||||
|
MyParam: asdf |
||||
|
|
||||
|
Parameters: |
||||
|
MyParam: |
||||
|
Type: String |
||||
|
Default: '' |
||||
|
Description: 'My parameter' |
||||
|
|
@ -0,0 +1,10 @@ |
|||||
|
cfnpp: |
||||
|
stack: |
||||
|
- doc4.yaml |
||||
|
variables: |
||||
|
C: 3 |
||||
|
D: 3 |
||||
|
|
||||
|
Resources: |
||||
|
NewResource: |
||||
|
Type: AWS::Custom |
@ -0,0 +1 @@ |
|||||
|
Resources: 1 |
@ -0,0 +1,133 @@ |
|||||
|
AWSTemplateFormatVersion: '2010-09-09' |
||||
|
Description: Deploy a service into an ECS cluster behind a private load balancer. |
||||
|
Parameters: |
||||
|
StackName: |
||||
|
Type: String |
||||
|
Default: production |
||||
|
Description: The name of the parent cluster stack that you created. Necessary |
||||
|
to locate and reference resources created by that stack. |
||||
|
ServiceName: |
||||
|
Type: String |
||||
|
Default: nginx |
||||
|
Description: A name for the service |
||||
|
ImageUrl: |
||||
|
Type: String |
||||
|
Default: nginx |
||||
|
Description: The url of a docker image that contains the application process that |
||||
|
will handle the traffic for this service |
||||
|
ContainerPort: |
||||
|
Type: Number |
||||
|
Default: 80 |
||||
|
Description: What port number the application inside the docker container is binding to |
||||
|
ContainerCpu: |
||||
|
Type: Number |
||||
|
Default: 256 |
||||
|
Description: How much CPU to give the container. 1024 is 1 CPU |
||||
|
ContainerMemory: |
||||
|
Type: Number |
||||
|
Default: 512 |
||||
|
Description: How much memory in megabytes to give the container |
||||
|
Path: |
||||
|
Type: String |
||||
|
Default: "*" |
||||
|
Description: A path on the public load balancer that this service |
||||
|
should be connected to. Use * to send all load balancer |
||||
|
traffic to this service. |
||||
|
Priority: |
||||
|
Type: Number |
||||
|
Default: 1 |
||||
|
Description: The priority for the routing rule added to the load balancer. |
||||
|
This only applies if your have multiple services which have been |
||||
|
assigned to different paths on the load balancer. |
||||
|
DesiredCount: |
||||
|
Type: Number |
||||
|
Default: 2 |
||||
|
Description: How many copies of the service task to run |
||||
|
Role: |
||||
|
Type: String |
||||
|
Default: "" |
||||
|
Description: (Optional) An IAM role to give the service's containers if the code within needs to |
||||
|
access other AWS resources like S3 buckets, DynamoDB tables, etc |
||||
|
|
||||
|
Conditions: |
||||
|
HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] |
||||
|
|
||||
|
Resources: |
||||
|
|
||||
|
# The task definition. This is a simple metadata description of what |
||||
|
# container to run, and what resource requirements it has. |
||||
|
TaskDefinition: |
||||
|
Type: AWS::ECS::TaskDefinition |
||||
|
Properties: |
||||
|
Family: !Ref 'ServiceName' |
||||
|
Cpu: !Ref 'ContainerCpu' |
||||
|
Memory: !Ref 'ContainerMemory' |
||||
|
TaskRoleArn: |
||||
|
Fn::If: |
||||
|
- 'HasCustomRole' |
||||
|
- !Ref 'Role' |
||||
|
- !Ref "AWS::NoValue" |
||||
|
ContainerDefinitions: |
||||
|
- Name: !Ref 'ServiceName' |
||||
|
Cpu: !Ref 'ContainerCpu' |
||||
|
Memory: !Ref 'ContainerMemory' |
||||
|
Image: !Ref 'ImageUrl' |
||||
|
PortMappings: |
||||
|
- ContainerPort: !Ref 'ContainerPort' |
||||
|
|
||||
|
# The service. The service is a resource which allows you to run multiple |
||||
|
# copies of a type of task, and gather up their logs and metrics, as well |
||||
|
# as monitor the number of running tasks and replace any that have crashed |
||||
|
Service: |
||||
|
Type: AWS::ECS::Service |
||||
|
DependsOn: LoadBalancerRule |
||||
|
Properties: |
||||
|
ServiceName: !Ref 'ServiceName' |
||||
|
Cluster: |
||||
|
Fn::ImportValue: |
||||
|
!Join [':', [!Ref 'StackName', 'ClusterName']] |
||||
|
DeploymentConfiguration: |
||||
|
MaximumPercent: 200 |
||||
|
MinimumHealthyPercent: 75 |
||||
|
DesiredCount: !Ref 'DesiredCount' |
||||
|
TaskDefinition: !Ref 'TaskDefinition' |
||||
|
LoadBalancers: |
||||
|
- ContainerName: !Ref 'ServiceName' |
||||
|
ContainerPort: !Ref 'ContainerPort' |
||||
|
TargetGroupArn: !Ref 'TargetGroup' |
||||
|
|
||||
|
# A target group. This is used for keeping track of all the tasks, and |
||||
|
# what IP addresses / port numbers they have. You can query it yourself, |
||||
|
# to use the addresses yourself, but most often this target group is just |
||||
|
# connected to an application load balancer, or network load balancer, so |
||||
|
# it can automatically distribute traffic across all the targets. |
||||
|
TargetGroup: |
||||
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup |
||||
|
Properties: |
||||
|
HealthCheckIntervalSeconds: 6 |
||||
|
HealthCheckPath: / |
||||
|
HealthCheckProtocol: HTTP |
||||
|
HealthCheckTimeoutSeconds: 5 |
||||
|
HealthyThresholdCount: 2 |
||||
|
Name: !Ref 'ServiceName' |
||||
|
Port: 80 |
||||
|
Protocol: HTTP |
||||
|
UnhealthyThresholdCount: 2 |
||||
|
VpcId: |
||||
|
Fn::ImportValue: |
||||
|
!Join [':', [!Ref 'StackName', 'VPCId']] |
||||
|
|
||||
|
# Create a rule on the load balancer for routing traffic to the target group |
||||
|
LoadBalancerRule: |
||||
|
Type: AWS::ElasticLoadBalancingV2::ListenerRule |
||||
|
Properties: |
||||
|
Actions: |
||||
|
- TargetGroupArn: !Ref 'TargetGroup' |
||||
|
Type: 'forward' |
||||
|
Conditions: |
||||
|
- Field: path-pattern |
||||
|
Values: [!Ref 'Path'] |
||||
|
ListenerArn: |
||||
|
Fn::ImportValue: |
||||
|
!Join [':', [!Ref 'StackName', 'PrivateListener']] |
||||
|
Priority: !Ref 'Priority' |
@ -0,0 +1,41 @@ |
|||||
|
- naked variable names |
||||
|
- single quoted strings |
||||
|
- numbers are numbers |
||||
|
- postfix? |
||||
|
- english-y operators? |
||||
|
|
||||
|
LoadBalancerType 'internet-facing' eq LoadBalancerType defined or |
||||
|
|
||||
|
Maybe function calls for some of the kind of stuff we need in expressions? |
||||
|
|
||||
|
'LoadBalancerType' parameter 'internet-facing' eq |
||||
|
|
||||
|
How do we differentiate between parameters and variables? Do we shove those all in one bucket? |
||||
|
|
||||
|
Should statements that reference parameters simply be evaluated as far as we can, then the |
||||
|
remainder converted into a cfn condition+fn::if? |
||||
|
|
||||
|
|
||||
|
Pass 1: |
||||
|
- Load all files |
||||
|
- Build dependency graph |
||||
|
|
||||
|
Pass 2: |
||||
|
- Run through files in dependency graph |
||||
|
- Run mergeFunctions + merge for each |
||||
|
|
||||
|
Pass 3: |
||||
|
- Load all variables (does this need a dependency graph as well?) -- can grab these from the final document |
||||
|
- Load all parameters (again, do we need a dependency graph?) -- can grab these from the final document |
||||
|
- Evaluate variables + parameters |
||||
|
|
||||
|
Pass 3: |
||||
|
- Run other functions (e.g., !var, !if, etc) |
||||
|
|
||||
|
So maybe pass 1: |
||||
|
mergeFunctions |
||||
|
merge |
||||
|
|
||||
|
Pass 2: |
||||
|
|
||||
|
|
@ -0,0 +1,67 @@ |
|||||
|
Okay so... |
||||
|
|
||||
|
Take document 1 + document 2 |
||||
|
Do a recursive merge |
||||
|
- Named properties overwrite scalars |
||||
|
- This is based on the _target_ type, if the source has a map and the target has a scalar... it overwrites the entire map with the scalar, |
||||
|
if the target is a map and the source is a scalar, the scalar is tossed away and overwritten |
||||
|
|
||||
|
Later, implement ways to control this merge. E.g., "!Replace" to replace an entire block. |
||||
|
|
||||
|
Things we might need: |
||||
|
!Replace -- replace entire block |
||||
|
!Unset -- remove value, any children, etc |
||||
|
|
||||
|
|
||||
|
|
||||
|
So: |
||||
|
``` |
||||
|
AwsWhatever: '2020-01-01' |
||||
|
Resources: |
||||
|
SecurityGroup: |
||||
|
VpcId: 1234 |
||||
|
Name: Test |
||||
|
UseSuperSecurity: True |
||||
|
Subnets: |
||||
|
- us-east-1a |
||||
|
- us-east-1b |
||||
|
Users: |
||||
|
Alice: 'aws:arn:1234:alice' |
||||
|
Bob: 'aws:arn:1234:bob' |
||||
|
``` |
||||
|
+ |
||||
|
``` |
||||
|
Resources: |
||||
|
SecurityGroup: |
||||
|
Name: NewGroup |
||||
|
UseSuperSecurity: !Unset ~ |
||||
|
NewValue: True |
||||
|
Subnets: |
||||
|
- us-east-1c |
||||
|
Users: !Replace |
||||
|
Charlie: 'aws:arn:1234:charlie' |
||||
|
``` |
||||
|
= |
||||
|
``` |
||||
|
AwsWhatever: '2020-01-01' |
||||
|
Resources: |
||||
|
SecurityGroup: |
||||
|
# Values not specified are unchanged |
||||
|
VpcId: 1234 |
||||
|
# Scalar values specified are replaced |
||||
|
Name: NewGroup |
||||
|
# !Unset removes a value/block/etc |
||||
|
# UserSuperSecurity: |
||||
|
# Values that don't exist in the original doc are added |
||||
|
NewValue: True |
||||
|
# Arrays are appended |
||||
|
Subnets: |
||||
|
- us-east-1a |
||||
|
- us-east-1b |
||||
|
- us-east-1c |
||||
|
# Replace stops the merge and just does a copy from the new document |
||||
|
Users: |
||||
|
Charlie: 'aws:arn:1234:charlie' |
||||
|
``` |
||||
|
|
||||
|
|
@ -0,0 +1,10 @@ |
|||||
|
cfnpp: |
||||
|
parameters: |
||||
|
CreateDnsRecord: whatever |
||||
|
variables: |
||||
|
A: true |
||||
|
Resources: |
||||
|
Asdf: False! |
||||
|
Metadata: |
||||
|
Stack: |
||||
|
- '' |
@ -0,0 +1,29 @@ |
|||||
|
app/CloudFormation/Client.php:6 PhanPluginNoCommentOnClass Class \App\CloudFormation\Client has no doc comment |
||||
|
app/CloudFormation/Client.php:8 PhanPluginNoCommentOnProtectedProperty Protected property \App\CloudFormation\Client->cfn has no doc comment |
||||
|
app/CloudFormation/Client.php:8 PhanPluginUnknownPropertyType Property \App\CloudFormation\Client->cfn has an initial type that cannot be inferred (Types inferred after analysis: \Aws\CloudFormation\CloudFormationClient) |
||||
|
app/CloudFormation/Client.php:20 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Client::getStack has no doc comment |
||||
|
app/CloudFormation/Client.php:34 PhanTypeMismatchArgumentInternal Argument 2 ($code) is $ex->getAwsErrorCode() of type null|string but \Exception::__construct() takes int |
||||
|
app/CloudFormation/Client.php:39 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Client::createStack has no doc comment |
||||
|
app/CloudFormation/Client.php:39 PhanPluginUnknownArrayMethodParamType Method \App\CloudFormation\Client::createStack has a parameter type of array for $parameters, but does not specify any key types or value types |
||||
|
app/CloudFormation/Client.php:42 PhanTypeMismatchReturnNullable Returning $this->getStack($response['StackId']) of type ?\App\CloudFormation\Stack but createStack() is declared to return \App\CloudFormation\Stack (expected returned value to be non-nullable) |
||||
|
app/CloudFormation/Client.php:45 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Client::updateStack has no doc comment |
||||
|
app/CloudFormation/Client.php:45 PhanPluginUnknownArrayMethodParamType Method \App\CloudFormation\Client::updateStack has a parameter type of array for $parameters, but does not specify any key types or value types |
||||
|
app/CloudFormation/Client.php:48 PhanTypeMismatchReturnNullable Returning $this->getStack($response['StackId']) of type ?\App\CloudFormation\Stack but updateStack() is declared to return \App\CloudFormation\Stack (expected returned value to be non-nullable) |
||||
|
app/CloudFormation/Client.php:52 PhanPluginNoCommentOnProtectedMethod Protected method \App\CloudFormation\Client::buildStackRequest has no doc comment |
||||
|
app/CloudFormation/Client.php:52 PhanPluginUnknownArrayMethodParamType Method \App\CloudFormation\Client::buildStackRequest has a parameter type of array for $parameters, but does not specify any key types or value types |
||||
|
app/CloudFormation/Client.php:52 PhanPluginUnknownArrayMethodReturnType Method \App\CloudFormation\Client::buildStackRequest() has a return type of array, but does not specify any key types or value types |
||||
|
app/CloudFormation/Stack.php:5 PhanPluginNoCommentOnClass Class \App\CloudFormation\Stack has no doc comment |
||||
|
app/CloudFormation/Stack.php:7 PhanPluginNoCommentOnProtectedProperty Protected property \App\CloudFormation\Stack->stack_data has no doc comment |
||||
|
app/CloudFormation/Stack.php:7 PhanPluginUnknownPropertyType Property \App\CloudFormation\Stack->stack_data has an initial type that cannot be inferred (Types inferred after analysis: array|array<string,mixed>) |
||||
|
app/CloudFormation/Stack.php:9 PhanPluginUnknownArrayMethodParamType Method \App\CloudFormation\Stack::__construct has a parameter type of array for $data, but does not specify any key types or value types |
||||
|
app/CloudFormation/Stack.php:14 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Stack::getId has no doc comment |
||||
|
app/CloudFormation/Stack.php:19 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Stack::getName has no doc comment |
||||
|
app/CloudFormation/Stack.php:24 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Stack::getStatus has no doc comment |
||||
|
app/CloudFormation/Stack.php:29 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Stack::getStatusReason has no doc comment |
||||
|
app/CloudFormation/Stack.php:34 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Stack::getParameters has no doc comment |
||||
|
app/CloudFormation/Stack.php:34 PhanPluginUnknownArrayMethodReturnType Method \App\CloudFormation\Stack::getParameters() has a return type of array, but does not specify any key types or value types |
||||
|
app/CloudFormation/Stack.php:45 PhanPluginNoCommentOnPublicMethod Public method \App\CloudFormation\Stack::getOutputs has no doc comment |
||||
|
app/CloudFormation/Stack.php:45 PhanPluginUnknownArrayMethodReturnType Method \App\CloudFormation\Stack::getOutputs() has a return type of array, but does not specify any key types or value types |
||||
|
app/Commands/TrashCommand.php:8 PhanPluginNoCommentOnClass Class \App\Commands\TrashCommand has no doc comment |
||||
|
app/Commands/TrashCommand.php:29 PhanPluginAlwaysReturnMethod Method \App\Commands\TrashCommand::handle has a return type of mixed, but may fail to return a value |
||||
|
app/Commands/TrashCommand.php:29 PhanTypeMissingReturn Method \App\Commands\TrashCommand::handle is declared to return mixed in phpdoc but has no return value |
@ -0,0 +1,25 @@ |
|||||
|
AWSTemplateFormatVersion: "2010-09-09" |
||||
|
Description: A sample template |
||||
|
Resources: |
||||
|
MyEC2Instance: #An inline comment |
||||
|
Type: "AWS::EC2::Instance" |
||||
|
Properties: |
||||
|
ImageId: "ami-0ff8a91507f77f867" #Another comment -- This is a Linux AMI |
||||
|
InstanceType: t2.micro |
||||
|
KeyName: testkey |
||||
|
BlockDeviceMappings: |
||||
|
- |
||||
|
DeviceName: /dev/sdm |
||||
|
Ebs: |
||||
|
VolumeType: io1 |
||||
|
Iops: 200 |
||||
|
DeleteOnTermination: false |
||||
|
VolumeSize: 20 |
||||
|
TestKey: !Replace asdf |
||||
|
OtherKey: !Replace |
||||
|
Test: 1 |
||||
|
Foo: "bar" |
||||
|
Nest: |
||||
|
- 1 |
||||
|
- 2 |
||||
|
- 3 |
@ -0,0 +1,9 @@ |
|||||
|
Resources: |
||||
|
Thing1: |
||||
|
Asdf: !Sub "test" |
||||
|
Foo: !Unset ~ |
||||
|
Fdsa: !Replace |
||||
|
- foo |
||||
|
- bar |
||||
|
- baz |
||||
|
|
@ -0,0 +1,20 @@ |
|||||
|
cfnpp: |
||||
|
parameters: |
||||
|
MultiAz: True |
||||
|
Param1: !var UseSecurity |
||||
|
variables: |
||||
|
UseSecurity: False |
||||
|
UseMultiAz: False |
||||
|
|
||||
|
Resources: |
||||
|
MyResource: |
||||
|
Name: A Resource |
||||
|
AvailabilityZones: !if |
||||
|
- MultiAz Param1 and |
||||
|
- |
||||
|
- us-east-1a |
||||
|
- us-east-1b |
||||
|
- |
||||
|
- us-east-1a |
||||
|
Test: !param Param1 |
||||
|
#Test2: !expr UseSecurity not |
@ -0,0 +1,11 @@ |
|||||
|
cfnpp: |
||||
|
parameters: |
||||
|
CreateDnsRecord: whatever |
||||
|
variables: |
||||
|
A: true |
||||
|
|
||||
|
Resources: |
||||
|
Asdf: !if |
||||
|
- CreateDnsRecord 1 and A or not |
||||
|
- True! |
||||
|
- False! |
Loading…
Reference in new issue