Compare commits

...

5 Commits

Author SHA1 Message Date
Adam Pippin 3fa0e7920e Bugfix: Fix processing order for functions -- deepest nodes first 3 years ago
Adam Pippin 949a8deb8f Re-add !expr function 3 years ago
Adam Pippin 0737fcd74a Add concat/select operators 3 years ago
Adam Pippin 2082f6238a Add boolean literals to expression parser 3 years ago
Adam Pippin 701b25779e Allow !replace to accept a scalar 3 years ago
  1. 25
      app/Cfnpp/Compiler.php
  2. 1
      app/Cfnpp/Expression/Expression.php
  3. 75
      app/Cfnpp/Expression/Token/BooleanLiteral.php
  4. 36
      app/Cfnpp/Expression/Token/OperatorBinary.php
  5. 40
      app/Cfnpp/Functions.php
  6. 74
      app/CloudFormation/Client.php
  7. 63
      app/CloudFormation/Stack.php
  8. 85
      app/Commands/Stack/Deploy.php
  9. 68
      app/Commands/TrashCommand.php
  10. 18
      doc1.yaml
  11. 14
      doc2.yaml
  12. 10
      doc3.yaml
  13. 1
      doc4.yaml
  14. 133
      ecs.yaml
  15. 41
      expressions.txt
  16. 67
      idea.txt
  17. 10
      out.yaml
  18. 29
      phan.txt
  19. 25
      test.yaml
  20. 9
      test2.yaml
  21. 20
      test3.yaml
  22. 11
      tmp.yml

25
app/Cfnpp/Compiler.php

@ -123,15 +123,6 @@ class Compiler implements \App\Engine\ICompile
$this->pass_2($document, $options);
return $this->document;
// Process each passed document
/*
foreach ($documents as $next_document)
{
$this->runMergeFunctions($document, $next_document);
$this->merge($document, $next_document);
$this->runFunctions($document);
}
*/
}
/**
@ -302,14 +293,12 @@ class Compiler implements \App\Engine\ICompile
{
$variables[] = $node->getValue();
}
/*
// TODO: Reimplement
elseif ($node instanceof NodeFunctionValue &&
$node->getName() == 'expr')
{
$expression = new \App\Cfnpp\Expression\Expression($node->getValue());
$variables = array_merge($variables, $expression->getReferencedVariables());
}*/
}
}
return $variables;
@ -454,6 +443,12 @@ class Compiler implements \App\Engine\ICompile
*/
protected function runFunctions(Node $node): void
{
$children = $node->getChildren();
foreach ($children as $child)
{
$this->runFunctions($child);
}
if ($node->isFunctionParent() && isset($this->functions[$node[0]->getName()]))
{
$function_node = $node[0];
@ -471,12 +466,6 @@ class Compiler implements \App\Engine\ICompile
return;
}
}
$children = $node->getChildren();
foreach ($children as $child)
{
$this->runFunctions($child);
}
}
/**

1
app/Cfnpp/Expression/Expression.php

@ -20,6 +20,7 @@ class Expression
* @var string[]
*/
public const TOKEN_TYPES = [
Token\BooleanLiteral::class,
Token\NumericLiteral::class,
Token\OperatorUnary::class,
Token\OperatorBinary::class,

75
app/Cfnpp/Expression/Token/BooleanLiteral.php

@ -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();
}
}

36
app/Cfnpp/Expression/Token/OperatorBinary.php

@ -22,7 +22,9 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
public const OPERATORS = [
'and',
'or',
'eq'
'eq',
'concat',
'select'
];
/**
@ -147,8 +149,32 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
return null;
case 'concat':
if (is_scalar($value1) && is_scalar($value2))
{
return $value2.$value1;
}
elseif ($value1 instanceof Parameter && is_scalar($value2) && empty($value2))
{
return $value1;
}
elseif ($value2 instanceof Parameter && is_scalar($value1) && empty($value1))
{
return $value2;
}
return null;
case 'select':
if (is_scalar($value1) && is_array($value2))
{
return $value2[$value1];
}
return null;
default:
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator());
throw new \Exception('Missing implementation for binary operator: '.$this->getOperator());
}
}
@ -174,8 +200,12 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
return ['Fn::Or' => [$value1, $value2]];
case 'eq':
return ['Fn::Equals' => [$value1, $value2]];
case 'concat':
return ['Fn::Join' => ['', [$value2, $value1]]];
case 'select':
return ['Fn::Select' => [$value1, $value2]];
default:
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator());
throw new \Exception('Operator cannot be applied to parameters: '.$this->getOperator());
}
}
}

40
app/Cfnpp/Functions.php

@ -83,7 +83,10 @@ class Functions
*/
public function mf_replace(Node $original, Node $target, NodeFunction $function): ?Node
{
// TODO: Deal with nodefunctionvalue
if ($function instanceof NodeFunctionValue)
{
return new NodeValue(null, $target->hasName() ? $target->getName() : null, $function->getValue());
}
$replacement = new Node(null, $target->hasName() ? $target->getName() : null);
$replacement->setChildren($function->getChildren());
@ -212,23 +215,38 @@ class Functions
* @param NodeFunction $function
* @return ?Node
*/
/*
* TODO: Reimplement
public function f_expr(Node $node, NodeFunction $function): ?Node
{
if (!($function instanceof NodeFunctionValue))
{
throw new \Exception('!if requires scalar argument');
throw new \Exception('!expr requires scalar argument');
}
$parser = new \App\Cfnpp\Expression\Parser();
$expression = $parser->parse($function->getValue());
$result = $expression->evaluate($this->options->getVariables());
$expression = new \App\Cfnpp\Expression\Expression($function->getValue(), $this->options);
$result = Node::fromPhp($result);
$result->setName($node->hasName() ? $node->getName() : null);
if ($expression->isComplete())
{
// If we computed a final value/set of values, we can just insert those
// directly.
if ($expression->count() == 1)
{
$solution_node = Node::fromPhp($expression->getValue());
}
else
{
$solution_node = Node::fromPhp($expression->toArray());
}
}
else
{
// Otherwise let's convert it to cfn intrinsics
$solution_node = Node::fromPhp($expression->toCloudformation());
}
return $result;
if ($node->hasName())
{
$solution_node->setName($node->getName());
}
return $solution_node;
}
*/
}

74
app/CloudFormation/Client.php

@ -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;
}
}

63
app/CloudFormation/Stack.php

@ -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;
}
}

85
app/Commands/Stack/Deploy.php

@ -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();
}
}

68
app/Commands/TrashCommand.php

@ -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();
}
}

18
doc1.yaml

@ -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

14
doc2.yaml

@ -0,0 +1,14 @@
cfnpp:
stack:
- doc1.yaml
variables:
A: 1
parameters:
MyParam: asdf
Parameters:
MyParam:
Type: String
Default: ''
Description: 'My parameter'

10
doc3.yaml

@ -0,0 +1,10 @@
cfnpp:
stack:
- doc4.yaml
variables:
C: 3
D: 3
Resources:
NewResource:
Type: AWS::Custom

1
doc4.yaml

@ -0,0 +1 @@
Resources: 1

133
ecs.yaml

@ -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'

41
expressions.txt

@ -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:

67
idea.txt

@ -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'
```

10
out.yaml

@ -0,0 +1,10 @@
cfnpp:
parameters:
CreateDnsRecord: whatever
variables:
A: true
Resources:
Asdf: False!
Metadata:
Stack:
- ''

29
phan.txt

@ -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

25
test.yaml

@ -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

9
test2.yaml

@ -0,0 +1,9 @@
Resources:
Thing1:
Asdf: !Sub "test"
Foo: !Unset ~
Fdsa: !Replace
- foo
- bar
- baz

20
test3.yaml

@ -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

11
tmp.yml

@ -0,0 +1,11 @@
cfnpp:
parameters:
CreateDnsRecord: whatever
variables:
A: true
Resources:
Asdf: !if
- CreateDnsRecord 1 and A or not
- True!
- False!
Loading…
Cancel
Save