stackmate 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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