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.
- data/CHANGELOG.txt +7 -0
- data/README.md +25 -8
- data/bin/stackmate.rb +57 -53
- data/lib/stackmate/aws_attribs.rb +31 -31
- data/lib/stackmate/classmap.rb +33 -25
- data/lib/stackmate/client.rb +80 -0
- data/lib/stackmate/intrinsic_functions.rb +94 -38
- data/lib/stackmate/logging.rb +19 -19
- data/lib/stackmate/participants/cloudstack.rb +250 -165
- data/lib/stackmate/participants/cloudstack_affinitygroup.rb +122 -0
- data/lib/stackmate/participants/cloudstack_autoscalepolicy.rb +113 -0
- data/lib/stackmate/participants/cloudstack_autoscalevmgroup.rb +86 -0
- data/lib/stackmate/participants/cloudstack_autoscalevmprofile.rb +140 -0
- data/lib/stackmate/participants/cloudstack_condition.rb +122 -0
- data/lib/stackmate/participants/cloudstack_egressfirewallrule.rb +149 -0
- data/lib/stackmate/participants/cloudstack_firewallrule.rb +149 -0
- data/lib/stackmate/participants/cloudstack_globalloadbalancerrule.rb +158 -0
- data/lib/stackmate/participants/cloudstack_instancegroup.rb +113 -0
- data/lib/stackmate/participants/cloudstack_ipaddress.rb +149 -0
- data/lib/stackmate/participants/cloudstack_ipforwardingrule.rb +131 -0
- data/lib/stackmate/participants/cloudstack_iptonic.rb +95 -0
- data/lib/stackmate/participants/cloudstack_iso.rb +95 -0
- data/lib/stackmate/participants/cloudstack_lbhealthcheckpolicy.rb +140 -0
- data/lib/stackmate/participants/cloudstack_lbstickinesspolicy.rb +122 -0
- data/lib/stackmate/participants/cloudstack_loadbalancer.rb +158 -0
- data/lib/stackmate/participants/cloudstack_loadbalancerrule.rb +185 -0
- data/lib/stackmate/participants/cloudstack_network.rb +293 -0
- data/lib/stackmate/participants/cloudstack_networkacl.rb +176 -0
- data/lib/stackmate/participants/cloudstack_networkacllist.rb +104 -0
- data/lib/stackmate/participants/cloudstack_nictovirtualmachine.rb +104 -0
- data/lib/stackmate/participants/cloudstack_portforwardingrule.rb +176 -0
- data/lib/stackmate/participants/cloudstack_project.rb +113 -0
- data/lib/stackmate/participants/cloudstack_remoteaccessvpn.rb +122 -0
- data/lib/stackmate/participants/cloudstack_securitygroup.rb +122 -0
- data/lib/stackmate/participants/cloudstack_securitygroupegress.rb +185 -0
- data/lib/stackmate/participants/cloudstack_securitygroupingress.rb +185 -0
- data/lib/stackmate/participants/cloudstack_snapshot.rb +113 -0
- data/lib/stackmate/participants/cloudstack_snapshotpolicy.rb +122 -0
- data/lib/stackmate/participants/cloudstack_sshkeypair.rb +113 -0
- data/lib/stackmate/participants/cloudstack_staticnat.rb +113 -0
- data/lib/stackmate/participants/cloudstack_staticroute.rb +95 -0
- data/lib/stackmate/participants/cloudstack_tags.rb +113 -0
- data/lib/stackmate/participants/cloudstack_template.rb +212 -0
- data/lib/stackmate/participants/cloudstack_togloballoadbalancerrule.rb +104 -0
- data/lib/stackmate/participants/cloudstack_toloadbalancerrule.rb +95 -0
- data/lib/stackmate/participants/cloudstack_virtualmachine.rb +348 -0
- data/lib/stackmate/participants/cloudstack_virtualmachineops.rb +95 -0
- data/lib/stackmate/participants/cloudstack_vmsnapshot.rb +113 -0
- data/lib/stackmate/participants/cloudstack_volume.rb +176 -0
- data/lib/stackmate/participants/cloudstack_volumeops.rb +111 -0
- data/lib/stackmate/participants/cloudstack_vpc.rb +158 -0
- data/lib/stackmate/participants/cloudstack_vpnconnection.rb +95 -0
- data/lib/stackmate/participants/cloudstack_vpncustomergateway.rb +176 -0
- data/lib/stackmate/participants/cloudstack_vpngateway.rb +86 -0
- data/lib/stackmate/participants/cloudstack_vpnuser.rb +122 -0
- data/lib/stackmate/participants/common.rb +219 -70
- data/lib/stackmate/participants/stacknest.rb +6 -0
- data/lib/stackmate/resolver.rb +122 -0
- data/lib/stackmate/stack.rb +116 -60
- data/lib/stackmate/stack_executor.rb +99 -37
- data/lib/stackmate/stackpi.rb +46 -0
- data/lib/stackmate/version.rb +1 -1
- data/lib/stackmate/waitcondition_server.rb +15 -15
- data/lib/stackmate.rb +1 -1
- data/stackmate.gemspec +2 -4
- metadata +70 -19
data/CHANGELOG.txt
ADDED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
2
1
|
# Stackmate - CloudFormation for CloudStack
|
3
2
|
|
4
|
-
A library designed to read
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
opt_parser.parse!(ARGV)
|
41
|
+
if ARGV.size == 1
|
42
|
+
stack_name = ARGV[0]
|
43
|
+
end
|
41
44
|
rescue => e
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
51
|
+
if options[:wait_conditions]
|
52
|
+
Thread.new do
|
53
|
+
StackMate::WaitConditionServer.run!
|
52
54
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
+
}
|
data/lib/stackmate/classmap.rb
CHANGED
@@ -1,30 +1,38 @@
|
|
1
1
|
module StackMate
|
2
|
-
|
3
|
-
|
2
|
+
PROFILES = ['CLOUDSTACK', 'NOOP']
|
3
|
+
@profile = 'CLOUDSTACK'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
48
|
+
first_ = false
|
49
|
+
end
|
50
|
+
result
|
36
51
|
end
|
37
52
|
|
38
53
|
def fn_getatt(array_value, workitem)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
data/lib/stackmate/logging.rb
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
3
|
module StackMate
|
4
|
-
module Logging
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module Logging
|
5
|
+
def logger
|
6
|
+
@logger ||= Logging.logger_for(self.class.name)
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
# Use a hash class-ivar to cache a unique Logger per class:
|
10
|
+
@loggers = {}
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
class << self
|
13
|
+
def logger_for(classname)
|
14
|
+
@loggers[classname] ||= configure_logger_for(classname)
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|