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