├── Rakefile ├── lib ├── aliyun │ ├── version.rb │ ├── aliyun_error.rb │ └── ecs.rb └── aliyun.rb ├── Gemfile ├── .gitignore ├── aliyun_api.gemspec ├── LICENSE.txt └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/aliyun/version.rb: -------------------------------------------------------------------------------- 1 | module Aliyun 2 | VERSION = "0.0.9" 3 | end 4 | -------------------------------------------------------------------------------- /lib/aliyun/aliyun_error.rb: -------------------------------------------------------------------------------- 1 | module Aliyun 2 | class AliyunError < RuntimeError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in aliyun-api.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | *.gem 16 | -------------------------------------------------------------------------------- /aliyun_api.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'aliyun/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "aliyun-api" 8 | spec.version = Aliyun::VERSION 9 | spec.authors = ["qjpcpu"] 10 | spec.email = ["qjpcpu@gmail.com"] 11 | spec.summary = %q{Aliyun ECS API} 12 | spec.description = %q{Used for aliyun ecs api} 13 | spec.homepage = "https://github.com/qjpcpu/aliyun-api" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_runtime_dependency 'rest-client' 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 qujianping 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/aliyun.rb: -------------------------------------------------------------------------------- 1 | require "aliyun/version" 2 | require "aliyun/aliyun_error" 3 | require "aliyun/ecs" 4 | require 'singleton' 5 | 6 | module Aliyun 7 | class Config 8 | include Singleton 9 | attr_accessor :request_parameters, :access_key_id, :access_key_secret, :endpoint_url, :request_method 10 | def initialize() 11 | self.request_parameters={ 12 | :Format=>"JSON", 13 | :Version=>"2014-05-26", 14 | :SignatureMethod=>"HMAC-SHA1", 15 | :SignatureVersion=>"1.0" 16 | } 17 | self.request_method = 'GET' 18 | self.endpoint_url = 'https://ecs.aliyuncs.com/' 19 | self.access_key_id = ENV['ALIYUN_ACCESS_KEY_ID'] 20 | self.access_key_secret = ENV['ALIYUN_ACCESS_KEY_SECRET'] 21 | end 22 | end 23 | def self.[](key) 24 | Config.instance.send key if Config.instance.respond_to? key 25 | end 26 | def self.[]=(key,value) 27 | key="#{key}=" 28 | Config.instance.send key,value if Config.instance.respond_to? key 29 | end 30 | def self.config(cfg) 31 | cfg.each do |k,v| 32 | self[k]=v 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/aliyun/ecs.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'securerandom' 3 | require 'uri' 4 | require 'rest_client' 5 | require 'base64' 6 | require 'openssl' 7 | require 'json' 8 | 9 | 10 | module Aliyun 11 | class ECS 12 | def initialize(options={}) 13 | Aliyun[:access_key_id] = options[:access_key_id] || Aliyun[:access_key_id] || ENV['ALIYUN_ACCESS_KEY_ID'] 14 | Aliyun[:access_key_secret] = options[:access_key_secret] || Aliyun[:access_key_secret] || ENV['ALIYUN_ACCESS_KEY_SECRET'] 15 | Aliyun[:endpoint_url] = options[:endpoint_url] || Aliyun[:endpoint_url] 16 | Aliyun[:request_parameters][:Version] = options[:version] || Aliyun[:version] 17 | 18 | end 19 | 20 | def method_missing(method_name, *args,&block) 21 | super if /[A-Z]/ =~ method_name.to_s 22 | method_name = method_name.to_s.split('_').map{|w| w.capitalize }.join('').gsub '_','' 23 | proxy_to_aliyun(method_name, args[0],&block) 24 | end 25 | 26 | private 27 | def proxy_to_aliyun(method_name, params,&block) 28 | params = build_request_parameters method_name, params 29 | block.call params if block 30 | begin 31 | res = RestClient.send Aliyun[:request_method].downcase, Aliyun[:endpoint_url], {:params => params,:verify_ssl => OpenSSL::SSL::VERIFY_PEER } 32 | return JSON.parse res.body if res.code == 200 33 | rescue RestClient::Exception => rcex 34 | raise AliyunError.new "response error: #{rcex.to_s}\nresponse detail: #{rcex.http_body}\nrequest parameters: #{params.reject{|k,v| k==:AccessKeyId}}" 35 | rescue =>e 36 | raise AliyunError.new e.to_s 37 | end 38 | end 39 | 40 | def build_request_parameters(method_name, params={}) 41 | params = Aliyun[:request_parameters].merge(params||{}) 42 | params.keys.each do |k| 43 | if /[A-Z]/ !~ k.to_s 44 | key=k.to_s.split('_').map{|w| w.capitalize }.join('').gsub('_','').to_sym 45 | params[key] = params.delete k 46 | end 47 | end 48 | params.merge!({ 49 | :AccessKeyId => Aliyun[:access_key_id], 50 | :Action => method_name.to_s, 51 | :SignatureNonce => SecureRandom.uuid, 52 | :TimeStamp => Time.now.utc.iso8601 53 | }) 54 | params[:Signature] = compute_signature params 55 | params 56 | end 57 | 58 | def compute_signature(params) 59 | params = params.inject({}){|memo,(k,v)| memo[k.to_sym]=v;memo} 60 | sorted_keys = params.keys.sort 61 | canonicalized_query_string = "" 62 | sorted_keys.each do |key| 63 | canonicalized_query_string << '&' 64 | canonicalized_query_string << percent_encode(key.to_s) 65 | canonicalized_query_string << '=' 66 | canonicalized_query_string << percent_encode(params[key]) 67 | end 68 | length = canonicalized_query_string.length 69 | string_to_sign = Aliyun[:request_method] + '&' + percent_encode('/') + '&' + percent_encode(canonicalized_query_string[1,length]) 70 | calculate_signature Aliyun[:access_key_secret]+"&", string_to_sign 71 | end 72 | 73 | def calculate_signature key, string_to_sign 74 | Base64.strict_encode64 OpenSSL::HMAC.digest('sha1',key, string_to_sign) 75 | end 76 | 77 | def percent_encode value 78 | value = URI.encode_www_form_component(value).gsub(/\+/,'%20').gsub(/\*/,'%2A').gsub(/%7E/,'~') 79 | end 80 | 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aliyun 2 | 3 | The Aliyun ECS API Client for Ruby 是调用 [阿里云 ECS服务](http://help.aliyun.com/view/11108189_13730407.html) 的 Ruby客户端类库. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'aliyun-api' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install aliyun-api 20 | 21 | ## Usage 22 | 23 | 首先,需要在代码中引入类库: 24 | 25 | ``` 26 | require 'aliyun' 27 | ``` 28 | 29 | ### 配置aliyun的access_key 30 | 31 | #### 1. 全局配置 32 | 33 | ``` 34 | options = { 35 | :access_key_id => "xxxxxx", 36 | :access_key_secret => "yyyyyy" 37 | } 38 | Aliyun.config options 39 | ecs = Aliyun::ECS.new 40 | ``` 41 | 42 | #### 2. 直接配置ECS客户端 43 | 44 | ``` 45 | options = { 46 | :access_key_id => "xxxxxx", 47 | :access_key_secret => "yyyyyy" 48 | } 49 | ecs = Aliyun::ECS.new options 50 | ``` 51 | 52 | #### 3. 环境变量 53 | 54 | 如果环境变量里`ALIYUN_ACCESS_KEY_ID`和`ALIYUN_ACCESS_KEY_SECRET`初始化了密钥,那么可以直接初始化ecs: 55 | 56 | ``` 57 | ecs = Aliyun::ECS.new 58 | ``` 59 | 60 | ### 调用ECS 61 | 62 | 这样, 你就可以根据 [阿里云弹性计算服务API参考手册](http://help.aliyun.com/view/11108189_13730407.html)初始化业务参数(除Action参数之外)为一个hash对象,并且将其作为参数传给Action方法(Action参数), action方法需要将阿里云手册中的Action名按ruby方式命名, 如:阿里云手册中的Action名`StartInstance`对应到这里的方法名为`start_instance`。 63 | 64 | (1) 例如查询可用地域列表,其Action参数为DescribeRegions,而没有其他参数,代码如下 65 | 66 | ``` 67 | ecs.describe_regions {} 68 | # 输出如下: 69 | {"Regions"=>{"Region"=>[{"LocalName"=>"深圳", "RegionId"=>"cn-shenzhen"}, {"LocalName"=>"青岛", "RegionId"=>"cn-qingdao"}, {"LocalName"=>"北京", "RegionId"=>"cn-beijing"}, {"LocalName"=>"香港", "RegionId"=>"cn-hongkong"}, {"LocalName"=>"杭州", "RegionId"=>"cn-hangzhou"}]}, "RequestId"=>"abcdefg"} 70 | ``` 71 | 72 | (2) 再比如查询可用镜像,代码如下 73 | 74 | ``` 75 | parameters = {:RegionId => "cn-beijing", :PageNumber => 2, :PageSize => 20} 76 | service.describe_images parameters 77 | # or use parameters in rubyway 78 | parameters = {:region_id => "cn-beijing", :page_number => 2, :page_size => 20} 79 | ``` 80 | 81 | (3) 任意API都可以传递一个block,该block可以用来调试实际生成的请求参数 82 | 83 | ``` 84 | ecs.describe_instances region_id: 'cn-hangzhou' do |params| 85 | puts params 86 | end 87 | # 输出 88 | {:Format=>"JSON", :Version=>"2014-05-26", :SignatureMethod=>"HMAC-SHA1", :SignatureVersion=>"1.0", :RegionId=>"cn-hangzhou", :AccessKeyId=>"xxxx", :Action=>"DescribeInstances", :SignatureNonce=>"aaaa-5ade-4391-a032-013a53e692db", :TimeStamp=>"2015-12-28T08:14:46Z", :Signature=>"+Spi/ee="} 89 | ``` 90 | 91 | ## ECS API列表(aliyun ecs api版本: 20140526) 92 | 93 | ### 实例相关接口 94 | #### 1.创建实例create_instance 95 | 参数列表 96 | * region_id,地域,required 97 | * zone_id,子区域,optional 98 | * image_id,镜像,required 99 | * instance_type, required 100 | * security_group_id, required 101 | * instance_name, optional 102 | * description, optional 103 | * 略 104 | 105 | 例: 106 | 107 | ecs.create_instance :region_id=>'cn-hangzhou',:image_id=>'img_identifier',:instance_type=>'t2.small',:security_group_id=>'sg_id' 108 | 109 | #### 2.启动实例start_instance 110 | 参数列表 111 | * instance_id,实例id,required 112 | 113 | 例: 114 | 115 | ecs.start_instance :instance_id=>'AYxxx' 116 | 117 | #### 3.停止实例stop_instance 118 | 参数列表 119 | * instance_id,实例id,required 120 | * force_stop,重启机器时是否强制关机,默认为false, optional 121 | 122 | 例: 123 | 124 | ecs.stop_instance :instance_id=>'AYxxx' 125 | 126 | #### 4.重启实例reboot_instance 127 | 参数列表 128 | * instance_id,实例id,required 129 | * force_stop,重启机器时是否强制关机,默认为false, optional 130 | 131 | 例: 132 | 133 | ecs.reboot_instance :instance_id=>'AYxxx' 134 | 135 | #### 5.修改实例属性modify_instance_attribute 136 | 参数列表 137 | * instance_id,实例id,required 138 | * instance_name,optional 139 | * description, optional 140 | * password, optional 141 | * host_name, optional 142 | 143 | 例: 144 | 145 | ecs.modify_instance_attribute :instance_id=>'AYxxx',:instance_name=>'new-name' 146 | 147 | #### 6.查询实例列表describe_instance_status 148 | 参数列表 149 | * region_id,地域id,required 150 | * zone_id,子区域,optional 151 | * page_number, optional 152 | * page_size, optional 153 | 154 | 例: 155 | 156 | ecs.describe_instance_status :region_id=>'cn-hangzhou' 157 | 158 | #### 7.查询实例信息describe_instance_attribute 159 | 参数列表 160 | * instance_id,实例id,required 161 | 162 | 例: 163 | 164 | ecs.describe_instance_attribute :instance_id=>'AYxxx' 165 | 166 | #### 8.删除实例delete_instance 167 | 参数列表 168 | * instance_id,实例id,required 169 | 170 | 例: 171 | 172 | ecs.delete_instance :instance_id=>'AYxxx' 173 | 174 | #### 9.将实例加入安全组join_security_group 175 | 参数列表 176 | * instance_id,实例id,required 177 | * security_group_id, required 178 | 179 | 例: 180 | 181 | ecs.join_security_group :instance_id=>'AYxxx',:security_group_id=>'sg_id' 182 | 183 | #### 10.将实例移出安全组leave_security_group 184 | 参数列表 185 | * instance_id,实例id,required 186 | * security_group_id, required 187 | 188 | 例: 189 | 190 | ecs.leave_security_group :instance_id=>'AYxxx',:security_group_id=>'sg_id' 191 | 192 | ### 磁盘相关接口 193 | #### 1.创建磁盘create_disk 194 | 参数列表 195 | * region_id,地域id,required 196 | * zone_id,子区域,optional 197 | * disk_name, optional 198 | * description, optional 199 | * size,磁盘大小GB, optional 200 | * snapshot_id,磁盘快照, optional 201 | * client_token, optional 202 | 203 | 例: 204 | 205 | ecs.create_disk :region_id=>'cn-hangzhou',:size=>100 206 | ecs.create_disk :region_id=>'cn-hangzhou',:snapshot_id=>'snap-id' 207 | 208 | #### 2.查询磁盘describe_disks 209 | 参数列表 210 | * region_id,地域id,required 211 | * zone_id,子区域,optional 212 | * disk_ids, optional 213 | * instance_id, optional 214 | * disk_type, all|system|data, optional,默认all 215 | * category, all|cloud|ephemeral,默认all, optional 216 | * status, In_use|Available|Attaching|Detaching|Creating|ReIniting|All, optional 217 | * snapshot_id, optional 218 | * portable, optional 219 | * delete_with_instance, 是否随实例释放, optional 220 | * delete_auto_snapshot, 删除磁盘时是否删除快照, optional 221 | * page_number, optional 222 | * page_size, optional 223 | 224 | 例: 225 | 226 | ecs.describe_disks :disk_ids=>["d1","d2"],:region_id=>'cn-hangzhou' 227 | 228 | (其他API类似,不再这里赘述,否则成了阿里的翻译机了^_^) 229 | 230 | ## Contributing 231 | 232 | 1. Fork it ( https://github.com/qjpcpu/aliyun-api/fork ) 233 | 2. Create your feature branch (`git checkout -b my-new-feature`) 234 | 3. Commit your changes (`git commit -am 'Add some feature'`) 235 | 4. Push to the branch (`git push origin my-new-feature`) 236 | 5. Create a new Pull Request 237 | --------------------------------------------------------------------------------