stackmate 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/CHANGELOG.txt +7 -0
  2. data/README.md +25 -8
  3. data/bin/stackmate.rb +57 -53
  4. data/lib/stackmate/aws_attribs.rb +31 -31
  5. data/lib/stackmate/classmap.rb +33 -25
  6. data/lib/stackmate/client.rb +80 -0
  7. data/lib/stackmate/intrinsic_functions.rb +94 -38
  8. data/lib/stackmate/logging.rb +19 -19
  9. data/lib/stackmate/participants/cloudstack.rb +250 -165
  10. data/lib/stackmate/participants/cloudstack_affinitygroup.rb +122 -0
  11. data/lib/stackmate/participants/cloudstack_autoscalepolicy.rb +113 -0
  12. data/lib/stackmate/participants/cloudstack_autoscalevmgroup.rb +86 -0
  13. data/lib/stackmate/participants/cloudstack_autoscalevmprofile.rb +140 -0
  14. data/lib/stackmate/participants/cloudstack_condition.rb +122 -0
  15. data/lib/stackmate/participants/cloudstack_egressfirewallrule.rb +149 -0
  16. data/lib/stackmate/participants/cloudstack_firewallrule.rb +149 -0
  17. data/lib/stackmate/participants/cloudstack_globalloadbalancerrule.rb +158 -0
  18. data/lib/stackmate/participants/cloudstack_instancegroup.rb +113 -0
  19. data/lib/stackmate/participants/cloudstack_ipaddress.rb +149 -0
  20. data/lib/stackmate/participants/cloudstack_ipforwardingrule.rb +131 -0
  21. data/lib/stackmate/participants/cloudstack_iptonic.rb +95 -0
  22. data/lib/stackmate/participants/cloudstack_iso.rb +95 -0
  23. data/lib/stackmate/participants/cloudstack_lbhealthcheckpolicy.rb +140 -0
  24. data/lib/stackmate/participants/cloudstack_lbstickinesspolicy.rb +122 -0
  25. data/lib/stackmate/participants/cloudstack_loadbalancer.rb +158 -0
  26. data/lib/stackmate/participants/cloudstack_loadbalancerrule.rb +185 -0
  27. data/lib/stackmate/participants/cloudstack_network.rb +293 -0
  28. data/lib/stackmate/participants/cloudstack_networkacl.rb +176 -0
  29. data/lib/stackmate/participants/cloudstack_networkacllist.rb +104 -0
  30. data/lib/stackmate/participants/cloudstack_nictovirtualmachine.rb +104 -0
  31. data/lib/stackmate/participants/cloudstack_portforwardingrule.rb +176 -0
  32. data/lib/stackmate/participants/cloudstack_project.rb +113 -0
  33. data/lib/stackmate/participants/cloudstack_remoteaccessvpn.rb +122 -0
  34. data/lib/stackmate/participants/cloudstack_securitygroup.rb +122 -0
  35. data/lib/stackmate/participants/cloudstack_securitygroupegress.rb +185 -0
  36. data/lib/stackmate/participants/cloudstack_securitygroupingress.rb +185 -0
  37. data/lib/stackmate/participants/cloudstack_snapshot.rb +113 -0
  38. data/lib/stackmate/participants/cloudstack_snapshotpolicy.rb +122 -0
  39. data/lib/stackmate/participants/cloudstack_sshkeypair.rb +113 -0
  40. data/lib/stackmate/participants/cloudstack_staticnat.rb +113 -0
  41. data/lib/stackmate/participants/cloudstack_staticroute.rb +95 -0
  42. data/lib/stackmate/participants/cloudstack_tags.rb +113 -0
  43. data/lib/stackmate/participants/cloudstack_template.rb +212 -0
  44. data/lib/stackmate/participants/cloudstack_togloballoadbalancerrule.rb +104 -0
  45. data/lib/stackmate/participants/cloudstack_toloadbalancerrule.rb +95 -0
  46. data/lib/stackmate/participants/cloudstack_virtualmachine.rb +348 -0
  47. data/lib/stackmate/participants/cloudstack_virtualmachineops.rb +95 -0
  48. data/lib/stackmate/participants/cloudstack_vmsnapshot.rb +113 -0
  49. data/lib/stackmate/participants/cloudstack_volume.rb +176 -0
  50. data/lib/stackmate/participants/cloudstack_volumeops.rb +111 -0
  51. data/lib/stackmate/participants/cloudstack_vpc.rb +158 -0
  52. data/lib/stackmate/participants/cloudstack_vpnconnection.rb +95 -0
  53. data/lib/stackmate/participants/cloudstack_vpncustomergateway.rb +176 -0
  54. data/lib/stackmate/participants/cloudstack_vpngateway.rb +86 -0
  55. data/lib/stackmate/participants/cloudstack_vpnuser.rb +122 -0
  56. data/lib/stackmate/participants/common.rb +219 -70
  57. data/lib/stackmate/participants/stacknest.rb +6 -0
  58. data/lib/stackmate/resolver.rb +122 -0
  59. data/lib/stackmate/stack.rb +116 -60
  60. data/lib/stackmate/stack_executor.rb +99 -37
  61. data/lib/stackmate/stackpi.rb +46 -0
  62. data/lib/stackmate/version.rb +1 -1
  63. data/lib/stackmate/waitcondition_server.rb +15 -15
  64. data/lib/stackmate.rb +1 -1
  65. data/stackmate.gemspec +2 -4
  66. metadata +70 -19
data/CHANGELOG.txt ADDED
@@ -0,0 +1,7 @@
1
+
2
+ = stackmate - CHANGELOG.txt
3
+
4
+
5
+ == stackmate - 0.1.0
6
+
7
+ - replace cloudstack_ruby_client with a simple built-in client
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
-
2
1
  # Stackmate - CloudFormation for CloudStack
3
2
 
4
- A library designed to read existing CloudFormation templates
3
+ A library designed to read CloudFormation templates
5
4
  and execute them on a CloudStack deployment.
5
+ Supports AWS Cloudformation templates (namespace AWS::Cloudformation) as well as the CloudStack:: native namespace.
6
6
  Uses the [ruote](http://ruote.rubyforge.org) workflow engine,
7
7
  and embeds a modular [Sinatra](http://www.sinatrarb.com/) application for wait handles.
8
8
 
@@ -11,7 +11,8 @@ Instead it runs everything on the client side.
11
11
 
12
12
  [Stacktician](https://github.com/chiradeep/stacktician) embeds stackmate to run it as a web application.
13
13
 
14
- Note that only Basic Zone (aka EC2-Classic) is supported for now.
14
+ For the AWS::CloudFormation namespace, only Basic Zone (aka EC2-Classic) is supported for now.
15
+ For the CloudStack:: namespace, all virtual resources that can be created in CloudStack 4.2 are supported.
15
16
 
16
17
  Follow:
17
18
  * \#cloudstack-dev on Freenode
@@ -59,7 +60,9 @@ $ export SECKEY=9iSsuImdUxU0oumHu0p11li4IoUtwcvrSHcU63ZHS_y-4Iz3w5xPROzyjZTUXkhI
59
60
  $ export URL="http://localhost:8080/client/api"
60
61
  ```
61
62
 
62
- * The supplied templates are taken from the AWS samples.
63
+ ## Sample Templates
64
+ ### AWS samples
65
+ AWS samples are templates that use the AWS::CloudFormation namespace.
63
66
 
64
67
  You need a couple of mappings from AWS ids to your CloudStack implementation:
65
68
 
@@ -73,6 +76,11 @@ templates:
73
76
  zoneid: b3409835-02b0-4d21-bba4-1f659402117e
74
77
  ```
75
78
 
79
+ ### CloudStack Samples
80
+ CloudStack samples use the CloudStack namespace. These templates typically require the zone id, service offering id and template id as input parameters
81
+
82
+
83
+ ## Usage Example
76
84
  * Ensure you have a ssh keypair called 'Foo' (used in the template parameter below) for your account FIRST:
77
85
 
78
86
  ```bash
@@ -83,7 +91,7 @@ $ cloudmonkey
83
91
  ```
84
92
 
85
93
 
86
- * Create a LAMP stack:
94
+ * Create a LAMP stack (AWS template)
87
95
 
88
96
  ```bash
89
97
  bin/stackmate MYSTACK01 --template-file=templates/LAMP_Single_Instance.template -p "DBName=cloud;DBUserName=cloud;SSHLocation=75.75.75.0/24;DBUsername=cloud;DBPassword=cloud;DBRootPassword=cloud;KeyName=Foo"
@@ -102,12 +110,22 @@ If you don't want the wait condition server to run, just use '-n'. Stackmate wil
102
110
 
103
111
  If you want to validate your template, you can use the --dry-run option. This will parse and validate the template and create an execution schedule.
104
112
 
113
+ ## Extending StackMate to other APIs
114
+
115
+ StackMate allows you to define your own workflow participants that will be called based on the template namespace. For example, you can define a class Foo::Bar
116
+ and use Foo::Bar as a "Type" in the template. The requirement is :
117
+ (1) It should contain a class variable @@stackmate_participant set to true so as to register with StackMate
118
+ (2) Foo::Bar should have Ruote::Participant as its ancestor
119
+ (3) Foo::Bar should have a method named "consume_workitem" that defines actions to be taken when called with workitem.
120
+
121
+ Use --plugins <Directories with ruby files> to add plugins. This has undergone limited testing
122
+
123
+
105
124
  ## TODO
106
125
  * Parallelize (with ruote concurrence) where possible
107
- * rollback on error
108
126
  * timeouts
109
127
  * embed in a web app ( [Stacktician](https://github.com/chiradeep/stacktician) )
110
- * add support for Advanced Zone templates (VPC), LB, etc
128
+
111
129
 
112
130
  ## Feedback & bug reports
113
131
 
@@ -142,7 +160,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
142
160
 
143
161
  - ruote, <http://ruote.rubyforge.org/>
144
162
  - sinatra, <http://www.sinatrarb.com/>
145
- - cloudstack_ruby_client, <https://github.com/chipchilders/cloudstack_ruby_client>
146
163
 
147
164
  Many thanks to the authors
148
165
 
data/bin/stackmate.rb CHANGED
@@ -9,66 +9,70 @@ require 'stackmate/waitcondition_server'
9
9
  options = {}
10
10
  stack_name = ''
11
11
  opt_parser = OptionParser.new do |opts|
12
- opts.banner = "Usage: stackmate.rb STACK_NAME [options]"
13
- opts.separator ""
14
- opts.separator "Specific options:"
15
- opts.on(:REQUIRED, "--template-file FILE", String, "Path to the file that contains the template") do |f|
16
- options[:file] = f
17
- end
18
- opts.on("-p", "--parameters [KEY1=VALUE1 KEY2=VALUE2..]", "Parameter values used to create the stack.") do |p|
19
- options[:params] = p
20
- puts p
21
- end
22
- options[:wait_conditions] = true
23
- opts.on("-n", "--no-wait-conditions", "Do not create any wait conditions") do
24
- options[:wait_conditions] = false
25
- end
26
- options[:dry_run] = false
27
- opts.on("-r", "--dry-run", "Parse and pretend to execute but not actually do anything. Useful for validating the template") do
28
- options[:dry_run] = true
29
- end
30
- opts.on("-h", "--help", "Show this message") do
31
- puts opts
32
- exit
33
- end
12
+ opts.banner = "Usage: stackmate.rb STACK_NAME [options]"
13
+ opts.separator ""
14
+ opts.separator "Specific options:"
15
+ opts.on(:REQUIRED, "--template-file FILE", String, "Path to the file that contains the template") do |f|
16
+ options[:file] = f
17
+ end
18
+ opts.on("-p", "--parameters [KEY1=VALUE1 KEY2=VALUE2..]", "Parameter values used to create the stack.") do |p|
19
+ options[:params] = p
20
+ puts p
21
+ end
22
+ options[:wait_conditions] = true
23
+ opts.on("-n", "--no-wait-conditions", "Do not create any wait conditions") do
24
+ options[:wait_conditions] = false
25
+ end
26
+ options[:dry_run] = false
27
+ opts.on("-r", "--dry-run", "Parse and pretend to execute but not actually do anything. Useful for validating the template") do
28
+ options[:dry_run] = true
29
+ end
30
+ opts.on("-h", "--help", "Show this message") do
31
+ puts opts
32
+ exit
33
+ end
34
+ opts.on("--plugins DIRS",String, "Comman separated plugins directory") do |plugins|
35
+ options[:plugins] = plugins
36
+ end
34
37
  end
35
38
 
36
39
  begin
37
- opt_parser.parse!(ARGV)
38
- if ARGV.size == 1
39
- stack_name = ARGV[0]
40
- end
40
+ opt_parser.parse!(ARGV)
41
+ if ARGV.size == 1
42
+ stack_name = ARGV[0]
43
+ end
41
44
  rescue => e
42
- puts e.message.capitalize
43
- puts opt_parser.help()
44
- exit 1
45
+ puts e.message.capitalize
46
+ puts opt_parser.help()
47
+ exit 1
45
48
  end
46
49
 
47
50
  if options[:file] && stack_name != ''
48
- if options[:wait_conditions]
49
- Thread.new do
50
- StackMate::WaitConditionServer.run!
51
- end
51
+ if options[:wait_conditions]
52
+ Thread.new do
53
+ StackMate::WaitConditionServer.run!
52
54
  end
53
- engine = Ruote::Dashboard.new(
54
- Ruote::Worker.new(
55
- Ruote::HashStorage.new))
56
- engine.noisy = ENV['NOISY'] == 'true'
55
+ end
56
+ engine = Ruote::Dashboard.new(
57
+ Ruote::Worker.new(
58
+ Ruote::HashStorage.new))
59
+ engine.noisy = ENV['NOISY'] == 'true'
60
+ engine.configure('wait_logger_timeout', 600)
57
61
 
58
- unknown = nil
59
- unresolved = catch(:unresolved) do
60
- unknown = catch(:unknown) do
61
- StackMate.configure('NOOP') if options[:dry_run]
62
- opts = {}
63
- api_opts = {:APIKEY => "#{ENV['APIKEY']}", :SECKEY => "#{ENV['SECKEY']}", :URL => "#{ENV['URL']}" }
64
- p = StackMate::StackExecutor.new(options[:file], stack_name, options[:params], engine, options[:wait_conditions], api_opts)
65
- p.launch()
66
- nil
67
- end
68
- nil
62
+ unknown = nil
63
+ unresolved = catch(:unresolved) do
64
+ unknown = catch(:unknown) do
65
+ StackMate.configure('NOOP') if options[:dry_run]
66
+ opts = {}
67
+ api_opts = {:APIKEY => "#{ENV['APIKEY']}", :SECKEY => "#{ENV['SECKEY']}", :URL => "#{ENV['URL']}" }
68
+ p = StackMate::StackExecutor.new(options[:file], stack_name, options[:params], engine, options[:wait_conditions], api_opts, options[:plugins])
69
+ p.launch()
70
+ nil
69
71
  end
70
- puts 'Failed to resolve parameters ' + unresolved.to_s if unresolved
71
- print "Sorry, I don't know how to create resources of type: ", unknown, "\n" if unknown
72
- else
73
- puts opt_parser.help()
74
- end
72
+ nil
73
+ end
74
+ puts 'Failed to resolve parameters ' + unresolved.to_s if unresolved
75
+ print "Sorry, I don't know how to create resources of type: ", unknown, "\n" if unknown
76
+ else
77
+ puts opt_parser.help()
78
+ end
@@ -1,33 +1,33 @@
1
- AWS_ATTRIBS = {'AWS::CloudFormation::WaitCondition' => [ 'Data'],
2
- 'AWS::CloudFormation::Stack' => ['Outputs.EmbeddedStackOutputName'],
3
- 'AWS::CloudFront::Distribution' => ['DomainName'],
4
- 'AWS::EC2::EIP' => ['AllocationId'],
5
- 'AWS::EC2::Instance' => ['AvailabilityZone', 'PrivateDnsName', 'PublicDnsName', 'PrivateIp', 'PublicIp'],
6
- 'AWS::EC2::AWS::EC2::SubnetNetworkAclAssociation' => ['AssociationId'],
7
- 'AWS::ElasticBeanstalk::Environment' => ['EndpointURL'],
8
- 'AWS::ElasticLoadBalancing::LoadBalancer' => ['CanonicalHostedZoneName', 'CanonicalHostedZoneNameID',
9
- 'DNSName', 'SourceSecurityGroup.GroupName', 'SourceSecurityGroup.OwnerAlias'],
10
- 'AWS::IAM::AccessKey' => ['SecretAccessKey'],
11
- 'AWS::IAM::Group' => ['Arn'],
12
- 'AWS::IAM::User' => ['Arn'],
13
- 'AWS::RDS::DBInstance' => ['Endpoint.Address', 'Endpoint.Port'],
14
- 'AWS::S3::Bucket' => ['DomainName', 'WebsiteURL', 'Arn'],
15
- 'AWS::SQS::Queue' => ['Arn', 'QueueName']
16
- }
1
+ AWS_ATTRIBS = {'AWS::CloudFormation::WaitCondition' => [ 'Data'],
2
+ 'AWS::CloudFormation::Stack' => ['Outputs.EmbeddedStackOutputName'],
3
+ 'AWS::CloudFront::Distribution' => ['DomainName'],
4
+ 'AWS::EC2::EIP' => ['AllocationId'],
5
+ 'AWS::EC2::Instance' => ['AvailabilityZone', 'PrivateDnsName', 'PublicDnsName', 'PrivateIp', 'PublicIp'],
6
+ 'AWS::EC2::AWS::EC2::SubnetNetworkAclAssociation' => ['AssociationId'],
7
+ 'AWS::ElasticBeanstalk::Environment' => ['EndpointURL'],
8
+ 'AWS::ElasticLoadBalancing::LoadBalancer' => ['CanonicalHostedZoneName', 'CanonicalHostedZoneNameID',
9
+ 'DNSName', 'SourceSecurityGroup.GroupName', 'SourceSecurityGroup.OwnerAlias'],
10
+ 'AWS::IAM::AccessKey' => ['SecretAccessKey'],
11
+ 'AWS::IAM::Group' => ['Arn'],
12
+ 'AWS::IAM::User' => ['Arn'],
13
+ 'AWS::RDS::DBInstance' => ['Endpoint.Address', 'Endpoint.Port'],
14
+ 'AWS::S3::Bucket' => ['DomainName', 'WebsiteURL', 'Arn'],
15
+ 'AWS::SQS::Queue' => ['Arn', 'QueueName']
16
+ }
17
17
 
18
18
 
19
- AWS_FAKE_ATTRIB_VALUES = {'AWS::CloudFormation::WaitCondition' => {'Data' => '{ "Signal1" : "Step 1 complete." , "Signal2" : "Step 2 complete." } '},
20
- 'AWS::CloudFormation::Stack' => {'Outputs.EmbeddedStackOutputName' => 'FakeEmbeddedStackOutputName'},
21
- 'AWS::CloudFront::Distribution' => {'DomainName' => 'd2fadu0nynjpfn.cloudfront.net'},
22
- 'AWS::EC2::EIP' => {'AllocationId' => 'eipalloc-5723d13e'},
23
- 'AWS::EC2::Instance' => {'AvailabilityZone' => 'us-east-1b', 'PrivateDnsName' => 'ip-10-24-34-0.cs.internal', 'PublicDnsName' => 'cs-17-10-90-145.compute.acs.org.', 'PrivateIp' => '10.24.34.0', 'PublicIp' => '75.75.75.111'},
24
- 'AWS::EC2::AWS::EC2::SubnetNetworkAclAssociation' => {'AssociationId' => 'aclassoc-e5b95c8c'},
25
- 'AWS::ElasticBeanstalk::Environment' => {'EndpointURL' => 'eb-myst-myen-132MQC4KRLAMD-1371280482.us-east-1.elb.amazonaws.com'},
26
- 'AWS::ElasticLoadBalancing::LoadBalancer' => {'CanonicalHostedZoneName' => 'mystack-myelb-15HMABG9ZCN57-1013119603.us-east-1.elb.amazonaws.com', 'CanonicalHostedZoneNameID' => 'Z3DZXE0Q79N41H', 'DNSName' => 'mystack-myelb-15HMABG9ZCN57-1013119603.us-east-1.elb.amazonaws.com', 'SourceSecurityGroup.GroupName' => 'elb-ssg', 'SourceSecurityGroup.OwnerAlias' => 'elb-ssg-owner'},
27
- 'AWS::IAM::AccessKey' => {'SecretAccessKey' => 'c8alrXUtnYEMI/K7MDAZQ/bPxRfiCYzEXAMPLEKEY'},
28
- 'AWS::IAM::Group' => {'Arn' => 'arn:aws:iam::123456789012:group/mystack-mygroup-1DZETITOWEKVO'},
29
- 'AWS::IAM::User' => {'Arn' => 'mystack-myuser-1CCXAFG2H2U4D'},
30
- 'AWS::RDS::DBInstance' => {'Endpoint.Address' => 'mystack-mydb-1apw1j4phylrk.cg034hpkmmjt.us-east-1.rds.amazonaws.com', 'Endpoint.Port' => '3306'},
31
- 'AWS::S3::Bucket' => {'DomainName' => 'mystack-mybucket-kdwwxmddtr2g.s3.amazonaws.com', 'WebsiteURL' => 'http://mystack-mybucket-kdwwxmddtr2g.s3-website-us-east-1.amazonaws.com/', 'Arn' => 'arn:aws:s3::12345678901::root'},
32
- 'AWS::SQS::Queue' => {'Arn' => 'arn:aws:sqs:us-east-1:123456789012:mystack-myqueue-15PG5C2FC1CW8', 'QueueName' => 'mystack-myqueue-1VF9BKQH5BJVI'}
33
- }
19
+ AWS_FAKE_ATTRIB_VALUES = {'AWS::CloudFormation::WaitCondition' => {'Data' => '{ "Signal1" : "Step 1 complete." , "Signal2" : "Step 2 complete." } '},
20
+ 'AWS::CloudFormation::Stack' => {'Outputs.EmbeddedStackOutputName' => 'FakeEmbeddedStackOutputName'},
21
+ 'AWS::CloudFront::Distribution' => {'DomainName' => 'd2fadu0nynjpfn.cloudfront.net'},
22
+ 'AWS::EC2::EIP' => {'AllocationId' => 'eipalloc-5723d13e'},
23
+ 'AWS::EC2::Instance' => {'AvailabilityZone' => 'us-east-1b', 'PrivateDnsName' => 'ip-10-24-34-0.cs.internal', 'PublicDnsName' => 'cs-17-10-90-145.compute.acs.org.', 'PrivateIp' => '10.24.34.0', 'PublicIp' => '75.75.75.111'},
24
+ 'AWS::EC2::AWS::EC2::SubnetNetworkAclAssociation' => {'AssociationId' => 'aclassoc-e5b95c8c'},
25
+ 'AWS::ElasticBeanstalk::Environment' => {'EndpointURL' => 'eb-myst-myen-132MQC4KRLAMD-1371280482.us-east-1.elb.amazonaws.com'},
26
+ 'AWS::ElasticLoadBalancing::LoadBalancer' => {'CanonicalHostedZoneName' => 'mystack-myelb-15HMABG9ZCN57-1013119603.us-east-1.elb.amazonaws.com', 'CanonicalHostedZoneNameID' => 'Z3DZXE0Q79N41H', 'DNSName' => 'mystack-myelb-15HMABG9ZCN57-1013119603.us-east-1.elb.amazonaws.com', 'SourceSecurityGroup.GroupName' => 'elb-ssg', 'SourceSecurityGroup.OwnerAlias' => 'elb-ssg-owner'},
27
+ 'AWS::IAM::AccessKey' => {'SecretAccessKey' => 'c8alrXUtnYEMI/K7MDAZQ/bPxRfiCYzEXAMPLEKEY'},
28
+ 'AWS::IAM::Group' => {'Arn' => 'arn:aws:iam::123456789012:group/mystack-mygroup-1DZETITOWEKVO'},
29
+ 'AWS::IAM::User' => {'Arn' => 'mystack-myuser-1CCXAFG2H2U4D'},
30
+ 'AWS::RDS::DBInstance' => {'Endpoint.Address' => 'mystack-mydb-1apw1j4phylrk.cg034hpkmmjt.us-east-1.rds.amazonaws.com', 'Endpoint.Port' => '3306'},
31
+ 'AWS::S3::Bucket' => {'DomainName' => 'mystack-mybucket-kdwwxmddtr2g.s3.amazonaws.com', 'WebsiteURL' => 'http://mystack-mybucket-kdwwxmddtr2g.s3-website-us-east-1.amazonaws.com/', 'Arn' => 'arn:aws:s3::12345678901::root'},
32
+ 'AWS::SQS::Queue' => {'Arn' => 'arn:aws:sqs:us-east-1:123456789012:mystack-myqueue-15PG5C2FC1CW8', 'QueueName' => 'mystack-myqueue-1VF9BKQH5BJVI'}
33
+ }
@@ -1,30 +1,38 @@
1
1
  module StackMate
2
- PROFILES = ['CLOUDSTACK', 'NOOP']
3
- @profile = 'CLOUDSTACK'
2
+ PROFILES = ['CLOUDSTACK', 'NOOP']
3
+ @profile = 'CLOUDSTACK'
4
4
 
5
- CS_CLASS_MAP = {
6
- 'AWS::CloudFormation::WaitConditionHandle' => 'StackMate::WaitConditionHandle',
7
- 'AWS::CloudFormation::WaitCondition' => 'StackMate::WaitCondition',
8
- 'AWS::EC2::Instance' => 'StackMate::CloudStackInstance',
9
- 'AWS::EC2::SecurityGroup' => 'StackMate::CloudStackSecurityGroup',
10
- 'Outputs' => 'StackMate::CloudStackOutput'
11
- }
5
+ CS_CLASS_MAP = {
6
+ 'AWS::CloudFormation::WaitConditionHandle' => 'StackMate::WaitConditionHandle',
7
+ 'AWS::CloudFormation::WaitCondition' => 'StackMate::WaitCondition',
8
+ 'AWS::EC2::Instance' => 'StackMate::CloudStackInstance',
9
+ 'AWS::EC2::SecurityGroup' => 'StackMate::CloudStackSecurityGroupAWS',
10
+ 'Outputs' => 'StackMate::CloudStackOutput',
11
+ }
12
12
 
13
- def StackMate.class_for(cf_resource)
14
- case @profile
15
- when 'CLOUDSTACK'
16
- return CS_CLASS_MAP[cf_resource]
17
- when 'NOOP'
18
- if cf_resource == 'Outputs'
19
- 'StackMate::Output'
20
- else
21
- 'StackMate::NoOpResource'
22
- end
23
- end
24
- end
13
+ def StackMate.class_for(cf_resource)
14
+ #return cf_resource
15
+ case @profile
16
+ when 'CLOUDSTACK'
17
+ if(cf_resource.start_with?("CloudStack::"))
18
+ c = cf_resource.split('::')[1]
19
+ "StackMate::CloudStack"+c
20
+ elsif(CS_CLASS_MAP.has_key?(cf_resource))
21
+ CS_CLASS_MAP[cf_resource]
22
+ else
23
+ cf_resource
24
+ end
25
+ when 'NOOP'
26
+ if cf_resource == 'Outputs'
27
+ 'StackMate::Output'
28
+ else
29
+ 'StackMate::NoOpResource'
30
+ end
31
+ end
32
+ end
25
33
 
26
- def StackMate.configure(profile)
27
- @profile = profile
28
- end
34
+ def StackMate.configure(profile)
35
+ @profile = profile
36
+ end
29
37
 
30
- end
38
+ end
@@ -0,0 +1,80 @@
1
+ %w[ base64 cgi openssl uri digest/sha1 net/https net/http json ].each { |f| require f }
2
+
3
+
4
+ module StackMate
5
+ class CloudStackClient
6
+ #
7
+ # The following is malformed response title in ACS, should be fixed
8
+ #
9
+ MALFORMED_RESPONSES = {
10
+ /(create|list)counter/i => 'counterresponse',
11
+ /createcondition/i => 'conditionresponse',
12
+ /createautoscalepolicy/i => 'autoscalepolicyresponse',
13
+ /createautoscalevmprofile/i => 'autoscalevmprofileresponse',
14
+ /createautoscalevmgroup/i => 'autoscalevmgroupresponse',
15
+ /enableautoscalevmgroup/i => 'enableautoscalevmGroupresponse',
16
+ /disableautoscalevmgroup/i => 'disableautoscalevmGroupresponse',
17
+ /assignvirtualmachine/i => 'moveuservmresponse',
18
+ /resetsshkeyforvirtualmachine/i => 'resetSSHKeyforvirtualmachineresponse',
19
+ /restorevirtualmachine/i => 'restorevmresponse',
20
+ /activateproject/i => 'activaterojectresponse',
21
+ /listnetworkdevice/i => 'listnetworkdevice',
22
+ /listniciranvpdevicenetworks/i => 'listniciranvpdevicenetworks',
23
+ /cancelstoragemaintenance/i => 'cancelprimarystoragemaintenanceresponse',
24
+ /enablestoragemaintenance/i => 'prepareprimarystorageformaintenanceresponse',
25
+ /copyiso/i => 'copytemplateresponse',
26
+ /deleteiso/i => 'deleteisosresponse',
27
+ /listisopermissions/i => 'listtemplatepermissionsresponse'
28
+ }
29
+ def initialize(api_url, api_key, secret_key, use_ssl=nil)
30
+ @api_url = api_url
31
+ @api_key = api_key
32
+ @secret_key = secret_key
33
+ @use_ssl = use_ssl
34
+ end
35
+
36
+ def request(params)
37
+ params['response'] = 'json'
38
+ params['apiKey'] = @api_key
39
+
40
+ data = params.map{ |k,v| "#{k.to_s}=#{CGI.escape(v.to_s).gsub(/\+|\ /, "%20")}" }.sort.join('&')
41
+
42
+ signature = OpenSSL::HMAC.digest 'sha1', @secret_key, data.downcase
43
+ signature = Base64.encode64(signature).chomp
44
+ signature = CGI.escape(signature)
45
+
46
+ url = "#{@api_url}?#{data}&signature=#{signature}"
47
+ uri = URI.parse(url)
48
+ http = Net::HTTP.new(uri.host, uri.port)
49
+ # http.use_ssl = @use_ssl
50
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
51
+ request = Net::HTTP::Get.new(uri.request_uri)
52
+
53
+ http.request(request)
54
+ end
55
+
56
+ def api_call(command,params)
57
+ #params = {'command' => command}
58
+ #params.merge!(args) unless args.empty?
59
+ params['command'] = command
60
+ response = request(params)
61
+ json = JSON.parse(response.body)
62
+ resp_title = command.downcase + "response"
63
+ MALFORMED_RESPONSES.each do |k, v|
64
+ if k =~ command
65
+ resp_title = v
66
+ end
67
+ end
68
+ if !response.is_a?(Net::HTTPOK)
69
+ if ((["431","530"].include?(response.code.to_s)) && (["9999","4350"].include?(json[resp_title]['cserrorcode'].to_s)))
70
+ raise ArgumentError, json[resp_title]['errortext']
71
+ end
72
+
73
+ raise RuntimeError, json['errorresponse']['errortext'] if response.code == "432"
74
+ raise RuntimeError, "Unable to make request from client due to :" + response.to_s
75
+ #raise CloudstackRubyClient::RequestError.new(response, json)
76
+ end
77
+ json[resp_title]
78
+ end
79
+ end
80
+ end
@@ -1,58 +1,114 @@
1
+ class Hash
2
+ def downcase_key
3
+ keys.each do |k|
4
+ store(k.downcase, Array === (v = delete(k)) ? v.map(&:downcase_key) : v)
5
+ end
6
+ self
7
+ end
8
+ end
9
+
1
10
  module StackMate
2
11
  module Intrinsic
3
12
 
4
- def intrinsic (hash, workitem)
5
- key = hash.keys[0]
6
- value = hash[key]
7
- #logger.debug "Intrinsic: key = #{key}, value = #{value}"
8
- case key
9
- when 'Fn::Join'
10
- fn_join(value, workitem)
11
- when 'Fn::GetAtt'
12
- fn_getatt(value, workitem)
13
- when 'Fn::Select'
14
- fn_select(value)
15
- when 'Ref'
16
- fn_ref(value, workitem)
17
- end
13
+ def intrinsic (hash, workitem)
14
+ key = hash.keys[0]
15
+ value = hash[key]
16
+ #logger.debug "Intrinsic: key = #{key}, value = #{value}"
17
+ case key
18
+ when 'Fn::Join'
19
+ fn_join(value, workitem)
20
+ when 'Fn::GetAtt'
21
+ fn_getatt(value, workitem)
22
+ when 'Fn::Select'
23
+ fn_select(value)
24
+ when 'Ref'
25
+ fn_ref(value, workitem)
26
+ when 'Fn::Lookup'
27
+ fn_lookup(value, workitem)
28
+ when 'Fn::FindInMap'
29
+ fn_map(value, workitem)
30
+ when 'Fn::Base64'
31
+ fn_base64(value, workitem)
32
+ end
18
33
  end
19
34
 
20
35
  def fn_join(value, workitem)
21
- delim = value[0]
22
- v = value[1]
23
- #logger.debug "Intrinsic: fn_join value = #{v}"
24
- result = ''
25
- first_ = true
26
- v.each do |token|
27
- case token
28
- when String
29
- result = result + (first_ ? "" : delim) + token
30
- when Hash
31
- result = result + (first_ ? "" : delim) + intrinsic(token, workitem)
32
- end
33
- first_ = false
36
+ delim = value[0]
37
+ v = value[1]
38
+ #logger.debug "Intrinsic: fn_join value = #{v}"
39
+ result = ''
40
+ first_ = true
41
+ v.each do |token|
42
+ case token
43
+ when String
44
+ result = result + (first_ ? "" : delim) + token
45
+ when Hash
46
+ result = result + (first_ ? "" : delim) + intrinsic(token, workitem)
34
47
  end
35
- result
48
+ first_ = false
49
+ end
50
+ result
36
51
  end
37
52
 
38
53
  def fn_getatt(array_value, workitem)
39
- resource = array_value[0]
40
- attribute = array_value[1]
41
- #logger.debug "Intrinsic: fn_getatt resource= #{resource} attrib = #{attribute} wi[Resource] = #{workitem[resource]}"
42
- workitem[resource][attribute]
54
+ resource = array_value[0]
55
+ attribute = array_value[1]
56
+ #logger.debug "Intrinsic: fn_getatt resource= #{resource} attrib = #{attribute} wi[Resource] = #{workitem[resource]}"
57
+ workitem[resource][attribute]
58
+ # attributes = array_value[1..-2]
59
+ # current_resource = workitem[resource]
60
+ # attributes.each do |a|
61
+ # current_resource = current_resource[a]
62
+ # end
63
+ # current_resource[array_value[-1]]
43
64
  end
44
65
 
45
66
  def fn_select(array_value)
46
- index = array_value[0].to_i #TODO unsafe
47
- values = array_value[1]
48
- #logger.debug "Intrinsic: fn_select index= #{index} values = #{values.inspect}"
49
- values[index]
67
+ index = array_value[0].to_i #TODO unsafe
68
+ values = array_value[1]
69
+ #logger.debug "Intrinsic: fn_select index= #{index} values = #{values.inspect}"
70
+ values[index]
50
71
  end
51
72
 
52
73
  def fn_ref(value, workitem)
53
- #logger.debug "Intrinsic: fn_ref value = #{value}"
74
+ #logger.debug "Intrinsic: fn_ref value = #{value}"
75
+ if workitem[value]
54
76
  workitem[value]['physical_id'] #TODO only works with Resources not Params
77
+ else
78
+ workitem['ResolvedNames'][value]
79
+ end
80
+ end
81
+
82
+ def fn_lookup(value, workitem)
83
+ case value
84
+ when String
85
+ workitem['ResolvedNames'][value]
86
+ when Hash
87
+ workitem['ResolvedNames'][intrinsic(value, workitem)]
88
+ end
89
+ end
90
+
91
+ def fn_map(value, workitem)
92
+ #logger.debug "Intrinsic: fn_ref value = #{value}"
93
+ resolved_keys = []
94
+ value.each do |k|
95
+ case k
96
+ when String
97
+ resolved_keys.push(k)
98
+ when Hash
99
+ resolved_keys.push(intrinsic(k,workitem))
100
+ end
101
+ end
102
+ workitem['Mappings'][resolved_keys[0]][resolved_keys[1]][resolved_keys[2]]
55
103
  end
56
104
 
105
+ def fn_base64(value, workitem)
106
+ case value
107
+ when String
108
+ Base64.urlsafe_encode64(value)
109
+ when Hash
110
+ Base64.urlsafe_encode64(intrinsic(value, workitem))
111
+ end
112
+ end
57
113
  end
58
- end
114
+ end
@@ -1,28 +1,28 @@
1
1
  require 'logger'
2
2
 
3
3
  module StackMate
4
- module Logging
5
- def logger
6
- @logger ||= Logging.logger_for(self.class.name)
7
- end
4
+ module Logging
5
+ def logger
6
+ @logger ||= Logging.logger_for(self.class.name)
7
+ end
8
8
 
9
- # Use a hash class-ivar to cache a unique Logger per class:
10
- @loggers = {}
9
+ # Use a hash class-ivar to cache a unique Logger per class:
10
+ @loggers = {}
11
11
 
12
- class << self
13
- def logger_for(classname)
14
- @loggers[classname] ||= configure_logger_for(classname)
15
- end
12
+ class << self
13
+ def logger_for(classname)
14
+ @loggers[classname] ||= configure_logger_for(classname)
15
+ end
16
16
 
17
- def configure_logger_for(classname)
18
- logger = Logger.new(STDOUT)
19
- logger.progname = classname
20
- logger.datetime_format= '%F %T'
21
- logger.formatter = proc do |severity, datetime, progname, msg|
22
- "[#{datetime}] #{severity} #{progname} #{msg}\n"
17
+ def configure_logger_for(classname)
18
+ logger = Logger.new(STDOUT)
19
+ logger.progname = classname
20
+ logger.datetime_format= '%F %T'
21
+ logger.formatter = proc do |severity, datetime, progname, msg|
22
+ "[#{datetime}] #{severity} #{progname} #{msg}\n"
23
+ end
24
+ logger
23
25
  end
24
- logger
25
26
  end
26
27
  end
27
- end
28
- end
28
+ end