myra 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66c47e80933981505a4ec2084b89b8f28f97f104
4
+ data.tar.gz: 323ee597703eecc3349a6d4fac6cab0f27ee92af
5
+ SHA512:
6
+ metadata.gz: 93fca4f67114e0ec435ffef14bf1e70dcca0772946f970d06a4e924a7d03999e3c06205cbe7c1c549730379972c561efdab0763101fe9239a215651a9c55eeec
7
+ data.tar.gz: a8ea3004eff91e90330d11e4ec5e191065d25d1ad05f15000673fb7ada24e0bc925ce0e151d50e237267bdf71cb4ecb07e9776423f4cd2ff853d948c1446d388
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,29 @@
1
+ cache:
2
+ paths:
3
+ - .bundler/
4
+
5
+ stages:
6
+ - lint
7
+ - test
8
+
9
+ rubocop:
10
+ stage: lint
11
+ before_script:
12
+ - gem install rubocop
13
+ script:
14
+ - ruby -v
15
+ - rubocop -v
16
+ - rubocop -c .rubocop.yml
17
+
18
+ rspec:
19
+ stage: test
20
+ before_script:
21
+ - ruby -v
22
+ - which ruby
23
+ - gem install bundler --no-ri --no-rdoc
24
+ - bundle install --jobs $(nproc) "${FLAGS[@]}" --path=.bundler
25
+ script:
26
+ - bundle exec rspec
27
+ artifacts:
28
+ paths:
29
+ - coverage/
@@ -0,0 +1,70 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ Exclude:
4
+ - '*.gemspec'
5
+ - 'Gemfile*'
6
+ - 'gems/**/*'
7
+ - 'test.rb'
8
+
9
+ # Offense count: 2
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
12
+ Lint/UnusedMethodArgument:
13
+ Exclude:
14
+
15
+ # Offense count: 6
16
+ # Configuration parameters: CountComments.
17
+ Metrics/MethodLength:
18
+ Max: 18
19
+
20
+ # Offense count: 1
21
+ # Cop supports --auto-correct.
22
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
23
+ # SupportedStyles: with_first_parameter, with_fixed_indentation
24
+ Style/AlignParameters:
25
+ Exclude:
26
+ - 'bin/rspec'
27
+
28
+ # Offense count: 2
29
+ # Cop supports --auto-correct.
30
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
31
+ # SupportedStyles: always, conditionals
32
+ Style/AndOr:
33
+ Exclude:
34
+ - 'bin/setup'
35
+ - 'bin/update'
36
+
37
+ # Offense count: 17
38
+ Style/Documentation:
39
+ Enabled: false
40
+
41
+ # Offense count: 6
42
+ # Cop supports --auto-correct.
43
+ # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues.
44
+ # SupportedStyles: ruby19, ruby19_no_mixed_keys, hash_rockets
45
+ Style/HashSyntax:
46
+ Enabled: false
47
+
48
+ # Offense count: 3
49
+ # Cop supports --auto-correct.
50
+ # Configuration parameters: SupportedStyles.
51
+ # SupportedStyles: call, braces
52
+ Style/LambdaCall:
53
+ EnforcedStyle: braces
54
+
55
+ # Offense count: 2
56
+ # Cop supports --auto-correct.
57
+ # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
58
+ # SupportedStyles: aligned, indented
59
+ Style/MultilineMethodCallIndentation:
60
+ Enabled: false
61
+
62
+ # Offense count: 1
63
+ # Cop supports --auto-correct.
64
+ # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
65
+ # SupportedStyles: space, no_space
66
+ Style/SpaceInsideBlockBraces:
67
+ Enabled: false
68
+
69
+ Style/RaiseArgs:
70
+ EnforcedStyle: compact
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ source 'https://rubygems.org'
3
+
4
+ # Specify your gem's dependencies in myra.gemspec
5
+ gemspec
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ # A sample Guardfile
3
+ # More info at https://github.com/guard/guard#readme
4
+
5
+ ## Uncomment and set this to only include directories you want to watch
6
+ # directories %w(app lib config test spec features) \
7
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
8
+
9
+ ## Note: if you are using the `directories` clause above and you are not
10
+ ## watching the project directory ('.'), then you will want to move
11
+ ## the Guardfile to a watched dir and symlink it back, e.g.
12
+ #
13
+ # $ mkdir config
14
+ # $ mv Guardfile config/
15
+ # $ ln -s config/Guardfile .
16
+ #
17
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
18
+
19
+ # Note: The cmd option is now required due to the increasing number of ways
20
+ # rspec may be run, below are examples of the most common uses.
21
+ # * bundler: 'bundle exec rspec'
22
+ # * bundler binstubs: 'bin/rspec'
23
+ # * spring: 'bin/rspec' (This will use spring if running and you have
24
+ # installed the spring binstubs per the docs)
25
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
26
+ # * 'just' rspec: 'rspec'
27
+
28
+ guard :rspec, cmd: 'bundle exec rspec' do
29
+ require 'guard/rspec/dsl'
30
+ dsl = Guard::RSpec::Dsl.new(self)
31
+
32
+ # Feel free to open issues for suggestions and improvements
33
+
34
+ # RSpec files
35
+ rspec = dsl.rspec
36
+ watch(rspec.spec_helper) { rspec.spec_dir }
37
+ watch(rspec.spec_support) { rspec.spec_dir }
38
+ watch(rspec.spec_files)
39
+
40
+ # Ruby files
41
+ ruby = dsl.ruby
42
+ dsl.watch_spec_files_for(ruby.lib_files)
43
+ end
44
+
45
+ guard :rubocop do
46
+ watch(/.+\.rb$/)
47
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
48
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Kloeckner-i GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,167 @@
1
+ # Myra
2
+
3
+ This gem allows for interaction with [MyraClouds](https://myracloud.com) API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'myra'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install myra
20
+
21
+ ## Usage
22
+
23
+ ### API Key and API secret
24
+
25
+ **Note:** Both can be obtained by contacting the MyraCloud support.
26
+
27
+ The gem assumes that you store the keys in your environment:
28
+
29
+ ```
30
+ ENV['MYRACLOUD_API_KEY']
31
+ ENV['MYRACLOUD_API_SECRET']
32
+ ```
33
+
34
+ However, if you wish to use something different, you can configure these values yourself:
35
+
36
+ ```ruby
37
+ Myra.configure do |config|
38
+ config.api_key = 'your-key-here'
39
+ config.api_secret = 'your-secret-here'
40
+ end
41
+ ```
42
+ ### Domains
43
+
44
+ A domain is the top level entity available. They cannot easily be edited, as only the `autoUpdate` can be set via API. Removing them will affect all the DNS records attached to that domain, so be extra careful.
45
+
46
+ #### List
47
+
48
+ ```ruby
49
+ list = Myra::Domains.list
50
+ # => [Domain, Domain, Domain, ...]
51
+
52
+ domain[0].name
53
+ # => 'my-awesome-name.com'
54
+ ```
55
+ #### Create
56
+
57
+ ```ruby
58
+ domain = Myra::Domain.new
59
+ domain.name = "www.kloeckner-i.com"
60
+ puts domain.id
61
+ # => nil
62
+ # [...]
63
+ domain = Myra::Domains.create(domain)
64
+ puts domain.id
65
+ ```
66
+
67
+ #### Update
68
+
69
+ Updating can only change the `autoUpdate` flag on a domain, if you need more extended editing, remove and recreate the domain (this is a limitation of the API)
70
+
71
+ ```ruby
72
+ list = Myra::Domains.list
73
+ # => [Domain, Domain, Domain, ...]
74
+
75
+ domain_to_update = domains.first
76
+ domain_to_update.auto_update = !domain_to_update.auto_update
77
+ Myra::Domains.update(domain_to_update)
78
+ ```
79
+
80
+ #### Delete
81
+
82
+ :warning: Deleting a domain is *dangerous*, as it will remove _all_ associated DNS records and other settings as well.
83
+
84
+ ```ruby
85
+ # if you don't know the id, just fetch them first
86
+ domain = Myra::Domain.new(id: 1234)
87
+ Myra::Domains.delete domain
88
+ ```
89
+
90
+ ### DNS Records
91
+
92
+ A DNS record comes in the form of `Myra::DnsRecord` and always belongs to a domain.
93
+
94
+ #### List
95
+
96
+ ```ruby
97
+ domain = Myra::Domain.new id: 1
98
+ records = Myra::DnsRecords.list(domain)
99
+ # => [Myra::DnsRecord, Myra::DnsRecord, ...]
100
+ ```
101
+
102
+ #### Create
103
+
104
+ ```ruby
105
+ domain = Myra::Domain.new id: 1
106
+ record = Myra::DnsRecord.new
107
+ record.name = 'foo' # full name will be infered from the domain you are creating it for
108
+ record.value = 'foo-bar-com.zep.ag'
109
+ record.type = Myra::DnsRecord::Type::CNAME # defaults to 'A'
110
+ Myra::DnsRecords.create(record, domain)
111
+ ```
112
+
113
+ #### Update
114
+
115
+ ```ruby
116
+ # get a domain, etc.
117
+ record = Myra::DnsRecords.list(domain).first
118
+ record.name = 'foo' # full name will be infered from the domain you are creating it for
119
+ record.value = 'foo-bar-com-2.zep.ag'
120
+ Myra::DnsRecords.update(record, domain)
121
+ ```
122
+
123
+ #### Delete
124
+
125
+ ```ruby
126
+ # get a domain, etc.
127
+ record = Myra::DnsRecords.list(domain).first
128
+ Myra::DnsRecords.delete(record, domain)
129
+ ```
130
+
131
+ ### Errors
132
+
133
+ All actions will raise proper errors when the API responds with an error. All violations will be presented as a `Myra::Violation` attached to the error.
134
+
135
+ #### Wrong credentials
136
+
137
+ If the API cannot be authenticated against, a `Myra::APIAuthError` will be thrown. Check your credentials of you encounter this error.
138
+
139
+ #### Failed API action
140
+
141
+ If an action against the API fails, a `Myra::APIActionError` will be thrown.
142
+
143
+ ```ruby
144
+ domain = Myra::Domain.new
145
+ domain.name = '.ff..'
146
+ begin
147
+ Myra::Domain.create(domain)
148
+ rescue Myra::APIActionError => e
149
+ puts e.message
150
+ puts e.violations # => [Myra::Violation, Myra::Violation, ...]
151
+ end
152
+ ```
153
+
154
+ ## Supported version for the MyraCloud API
155
+
156
+ The currently supported version for the API is *1.4*.
157
+
158
+ ## Development
159
+
160
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
161
+
162
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
163
+
164
+ ## License
165
+
166
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
167
+
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'myra'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'myra/version'
3
+ require 'myra/configuration'
4
+ require 'myra/request'
5
+
6
+ # shared actions
7
+ require 'myra/actions/shared/request_handler'
8
+ require 'myra/actions/shared/domain_handler'
9
+
10
+ # actions which can be performed
11
+ require 'myra/actions/domains'
12
+ require 'myra/actions/dns_records'
13
+
14
+ # objects which can be retrieved
15
+ require 'myra/objects/domain'
16
+ require 'myra/objects/dns_record'
17
+
18
+ # possible errors
19
+ require 'myra/objects/errors'
20
+
21
+ # and their violations
22
+ require 'myra/objects/violation'
23
+
24
+ # Myra is the top level module for this gem
25
+ module Myra
26
+ BASE_URL = 'https://api.myracloud.com'
27
+ PATH = '/en/rapi'
28
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ module DnsRecords
4
+ extend RequestHandler
5
+ extend DomainHandler
6
+ PATH = '/dnsRecords/{domain}'
7
+
8
+ def self.list(domain)
9
+ values = handle Request.new(path: path(domain))
10
+ values['list'].map { |record| DnsRecord.from_hash(record) }
11
+ end
12
+
13
+ def self.create(record, domain)
14
+ record = normalize_domain_name(record, domain)
15
+ request = Request.new(path: path(domain), type: :put)
16
+ request.payload = Oj.dump(record.to_hash)
17
+ value = handle request
18
+ DnsRecord.from_hash(value['targetObject'].first)
19
+ end
20
+
21
+ def self.update(domain, record)
22
+ record = normalize_domain_name(record, domain)
23
+ request = Request.new(path: path(domain), type: :post)
24
+ request.payload = Oj.dump(record.to_hash)
25
+ value = handle request
26
+ DnsRecord.from_hash(value['targetObject'].first)
27
+ end
28
+
29
+ def self.delete(domain, record)
30
+ r = normalize_domain_name(record, domain)
31
+ request = Request.new(path: path(domain), type: :delete)
32
+ keys = %w(modified id)
33
+ request.payload = Oj.dump(r.to_hash.select { |k, _| keys.include? k })
34
+ value = handle request
35
+ deleted_record = DnsRecord.from_hash(value['targetObject'].first)
36
+ deleted_record.deleted = true
37
+ deleted_record
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ require 'oj'
3
+
4
+ module Myra
5
+ module Domains
6
+ extend RequestHandler
7
+ PATH = '/domains'
8
+
9
+ def self.list
10
+ values = handle Request.new(path: PATH)
11
+ values['list'].map { |domain| Domain.from_hash(domain) }
12
+ end
13
+
14
+ def self.create(domain)
15
+ request = Request.new(path: PATH, type: :put)
16
+ request.payload = Oj.dump(domain.to_hash)
17
+ value = handle request
18
+ Domain.from_hash(value['targetObject'].first)
19
+ end
20
+
21
+ def self.delete(domain)
22
+ request = Request.new(path: PATH, type: :delete)
23
+ payload = domain.to_hash.select { |k, _| %w(id modified).include?(k) }
24
+ request.payload = Oj.dump(payload)
25
+ value = handle request
26
+ Domain.from_hash(value['targetObject'].first)
27
+ end
28
+
29
+ def self.update(domain)
30
+ request = Request.new path: PATH, type: :post
31
+ payload = domain.to_hash.select do |k, _|
32
+ %w(id modified autoUpdate).include? k
33
+ end
34
+ request.payload = Oj.dump(payload)
35
+ value = handle request
36
+ Domain.from_hash(value['targetObject'].first)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ module DomainHandler
4
+ def path(domain)
5
+ const_get('PATH').gsub('{domain}', domain.name)
6
+ end
7
+
8
+ def normalize_domain_name(record, domain)
9
+ return record if record.name.nil?
10
+ return record if record.name.include?(domain.name)
11
+ record.name = "#{record.name}.#{domain.name}"
12
+ record
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ module RequestHandler
4
+ def handle(request)
5
+ response = request.do
6
+ raise APIAuthError if response.status == 403
7
+ values = Oj.load(response.body)
8
+ errors values
9
+ end
10
+
11
+ def errors(values)
12
+ return values unless values['error']
13
+ violations = values['violationList'].map do |v|
14
+ Myra::Violation.from_hash v
15
+ end
16
+ raise APIActionError.new(violations)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ class << self
4
+ attr_writer :configuration
5
+
6
+ def configure
7
+ yield(configuration)
8
+ end
9
+
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def reset_configuration!
15
+ @configuration = Configuration.new
16
+ end
17
+ end
18
+
19
+ # Configuration provides the API credentials to MyraCloud
20
+ class Configuration
21
+ attr_accessor :api_key, :api_secret
22
+
23
+ def initialize
24
+ @api_key = ENV['MYRACLOUD_API_KEY']
25
+ @api_secret = ENV['MYRACLOUD_API_SECRET']
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ class DnsRecord
4
+ PATH = '/dnsRecords'
5
+
6
+ # Record types
7
+ class Type
8
+ CNAME = 'CNAME'
9
+ A = 'A'
10
+ end
11
+
12
+ attr_reader :id
13
+ attr_accessor :name, :value, :ttl, :type, :alternative_cname,
14
+ :active, :modified, :created
15
+ attr_writer :deleted
16
+
17
+ alias active? active
18
+
19
+ MAP = {
20
+ # response field => target field
21
+ 'name' => 'name',
22
+ 'value' => 'value',
23
+ 'ttl' => 'ttl',
24
+ 'alternativeCname' => 'alternative_cname',
25
+ 'active' => 'active',
26
+ 'recordType' => 'type'
27
+ }.freeze
28
+
29
+ def initialize(id: nil)
30
+ @id = id
31
+ @active = true
32
+ @type = Type::A
33
+ @ttl = 300
34
+ @deleted = false
35
+ end
36
+
37
+ def self.from_hash(hash)
38
+ domain = new(id: hash['id'])
39
+ hash.each do |k, v|
40
+ next unless MAP.key? k
41
+ domain.send "#{MAP[k]}=", v
42
+ end
43
+ domain.modified = DateTime.parse(hash['modified'])
44
+ domain.created = DateTime.parse(hash['created'])
45
+ domain
46
+ end
47
+
48
+ def to_hash
49
+ return new_record_hash if id.nil?
50
+ record_hash
51
+ end
52
+
53
+ def deleted?
54
+ @deleted
55
+ end
56
+
57
+ private
58
+
59
+ def new_record_hash
60
+ Hash[MAP.map { |k, v| [k, send(v)] }].reject { |_, v| v.nil? }
61
+ end
62
+
63
+ def record_hash
64
+ hash = new_record_hash
65
+ hash['id'] = id
66
+ hash['modified'] = modified.to_s
67
+ hash['created'] = created.to_s
68
+ hash
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require 'date'
3
+ module Myra
4
+ class Domain
5
+ PATH = '/domains'
6
+
7
+ attr_reader :id
8
+ attr_accessor :modified, :created, :name, :auto_update, :maintenance,
9
+ :paused, :owned, :reversed, :auto_dns, :paused_until
10
+
11
+ %w(auto_update maintenance owned paused reversed auto_dns).each do |boolean|
12
+ alias_method "#{boolean}?", boolean
13
+ end
14
+
15
+ MAP = {
16
+ # response field => target field
17
+ 'name' => 'name',
18
+ 'autoUpdate' => 'auto_update',
19
+ 'maintenance' => 'maintenance',
20
+ 'owned' => 'owned',
21
+ 'reversed' => 'reversed',
22
+ 'paused' => 'paused',
23
+ 'autoDns' => 'auto_dns'
24
+ }.freeze
25
+
26
+ def initialize(id: nil)
27
+ @id = id
28
+ %w(maintenance auto_update auto_dns paused owned reversed).each do |field|
29
+ send("#{field}=", false)
30
+ end
31
+ end
32
+
33
+ def self.from_hash(hash)
34
+ domain = new(id: hash['id'])
35
+ %w(modified created).each do |date_field|
36
+ domain.send "#{date_field}=", DateTime.parse(hash[date_field])
37
+ end
38
+ MAP.each do |k, v|
39
+ domain.send "#{v}=", hash[k] if hash.key?(k)
40
+ end
41
+ domain
42
+ end
43
+
44
+ def to_hash
45
+ return new_domain_hash if id.nil?
46
+ domain_hash
47
+ end
48
+
49
+ private
50
+
51
+ def new_domain_hash
52
+ Hash[MAP.map { |k, v| [k, send(v)] }]
53
+ end
54
+
55
+ def domain_hash
56
+ hash = new_domain_hash
57
+ hash['id'] = id
58
+ hash['modified'] = modified.to_s
59
+ hash['created'] = created.to_s
60
+ hash
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ class APIAuthError < StandardError
4
+ def message
5
+ 'Could not authenticate with the API, check your credentials'
6
+ end
7
+ end
8
+
9
+ class APIActionError < StandardError
10
+ attr_reader :violations
11
+
12
+ def initialize(
13
+ violations,
14
+ message = 'An error occured while processing your request'
15
+ )
16
+ super(message)
17
+ @violations = violations
18
+ end
19
+ end
20
+
21
+ class InvalidRequestTypeError < StandardError
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ class Violation
4
+ attr_reader :property, :message
5
+
6
+ def initialize(property, message)
7
+ @property = property
8
+ @message = message
9
+ end
10
+
11
+ def self.from_hash(violation)
12
+ new(violation['propertyPath'], violation['message'])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'myra/request/signature'
4
+ require 'myra/request/http'
5
+ require 'date'
6
+
7
+ module Myra
8
+ class Request
9
+ attr_reader :date, :api_key, :api_secret, :path
10
+ attr_accessor :type, :payload
11
+
12
+ ALLOWED_TYPES = [
13
+ :get,
14
+ :post,
15
+ :put,
16
+ :options,
17
+ :head,
18
+ :delete
19
+ ].freeze
20
+
21
+ def initialize(path:, type: :get)
22
+ @date = DateTime.now.to_s
23
+ @api_key = Myra.configuration.api_key
24
+ @api_secret = Myra.configuration.api_secret
25
+ @type = type
26
+ @path = path
27
+ end
28
+
29
+ def signing_string
30
+ [
31
+ md5.(payload),
32
+ verb,
33
+ "#{Myra::PATH}#{path}",
34
+ content_type,
35
+ date
36
+ ].join '#'
37
+ end
38
+
39
+ def type=(type)
40
+ raise InvalidRequestTypeError unless ALLOWED_TYPES.include?(type)
41
+ @type = type
42
+ end
43
+
44
+ def do
45
+ HTTP.new(self).response
46
+ end
47
+
48
+ def content_type
49
+ 'application/json'
50
+ end
51
+
52
+ def uri
53
+ "#{Myra::BASE_URL}#{Myra::PATH}#{path}"
54
+ end
55
+
56
+ def payload
57
+ return '' unless with_payload?
58
+ @payload
59
+ end
60
+
61
+ def with_payload?
62
+ [:post, :put, :delete].include?(type)
63
+ end
64
+
65
+ private
66
+
67
+ def md5
68
+ Digest::MD5.method(:hexdigest)
69
+ end
70
+
71
+ def verb
72
+ type.to_s.upcase
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ require 'faraday'
3
+ require 'oj'
4
+
5
+ module Myra
6
+ class Request
7
+ class HTTP
8
+ def initialize(request)
9
+ @request = request
10
+ @signature = Signature.new(
11
+ secret: Myra.configuration.api_secret,
12
+ date: request.date
13
+ )
14
+ end
15
+
16
+ def response
17
+ @response ||= perform_request
18
+ end
19
+
20
+ private
21
+
22
+ def perform_request
23
+ conn.send(@request.type) do |req|
24
+ req.headers['Content-Type'] = @request.content_type
25
+ req.headers['Date'] = @request.date.to_s
26
+ req.headers['Authorization'] = auth_header
27
+ req.body = @request.payload if @request.with_payload?
28
+ end
29
+ end
30
+
31
+ def conn
32
+ Faraday.new(url: @request.uri)
33
+ end
34
+
35
+ def api_key
36
+ @request.api_key
37
+ end
38
+
39
+ def auth_signature
40
+ @signature.for @request.signing_string
41
+ end
42
+
43
+ def auth_header
44
+ "MYRA #{api_key}:#{auth_signature}"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ require 'base64'
3
+ require 'openssl'
4
+
5
+ module Myra
6
+ class Request
7
+ class Signature
8
+ REQUEST_STRING = 'myra-api-request'
9
+ attr_reader :secret, :date, :base
10
+
11
+ def initialize(secret:, date:, base: 'MYRA')
12
+ @secret = secret
13
+ @date = date
14
+ @base = base
15
+ end
16
+
17
+ def date_key
18
+ method.(digest, "#{base}#{secret}", date)
19
+ end
20
+
21
+ def signing_key
22
+ method.(digest, date_key, REQUEST_STRING)
23
+ end
24
+
25
+ def for(signing_string)
26
+ base64.(hmac_method.(digest('sha512'), signing_key, signing_string))
27
+ end
28
+
29
+ private
30
+
31
+ def digest(type = 'sha256')
32
+ OpenSSL::Digest.new(type)
33
+ end
34
+
35
+ def method
36
+ OpenSSL::HMAC.method(:hexdigest)
37
+ end
38
+
39
+ def hmac_method
40
+ OpenSSL::HMAC.method(:digest)
41
+ end
42
+
43
+ def base64
44
+ Base64.method(:strict_encode64)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Myra
3
+ VERSION = '0.1.1'
4
+ end
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'myra/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'myra'
9
+ spec.version = Myra::VERSION
10
+ spec.authors = ['Florian Kraft']
11
+ spec.email = ['florian.kraft@kloeckner.com']
12
+
13
+ spec.summary = 'Gem for interacting with the MyraCloud API'
14
+ spec.description = %(
15
+ This gem allows for interacting with the MyraCloud
16
+ API to manipulate website and their DNS entries, etc.
17
+ It should take care of hasing requests to meet all of the
18
+ requirements set in place by the API. See myracloud.com
19
+ for more.
20
+ )
21
+ spec.homepage = 'https://github.com/kloeckner-i/myra'
22
+ spec.license = 'MIT'
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features)/})
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.12'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.0'
34
+ spec.add_development_dependency 'rubocop', '~> 0.41.1'
35
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
36
+ spec.add_development_dependency 'guard-rubocop', '~> 1.0'
37
+ spec.add_development_dependency 'webmock', '~> 2.1'
38
+ spec.add_development_dependency 'simplecov', '~> 0.12'
39
+
40
+ spec.add_dependency 'faraday', '~> 0.8.11'
41
+ spec.add_dependency 'oj', '~> 2.16'
42
+ end
data/test.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require 'openssl'
3
+ require 'base64'
4
+ require 'pry'
5
+ require 'faraday'
6
+
7
+ API_KEY = ENV['MYRACLOUD_API_KEY'].freeze
8
+ API_SECRET = ENV['MYRACLOUD_API_SECRET'].freeze
9
+ API_URL = 'https://api.myracloud.com'
10
+ $date = DateTime.now.to_s
11
+
12
+ def uri
13
+ '/en/rapi/domains'
14
+ end
15
+
16
+ def signing_string
17
+ [
18
+ 'd41d8cd98f00b204e9800998ecf8427e',
19
+ 'GET',
20
+ uri,
21
+ 'application/json',
22
+ $date
23
+ ].join('#')
24
+ end
25
+
26
+ def signing_key
27
+ intermediate = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), "MYRA#{API_SECRET}", $date)
28
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), intermediate, 'myra-api-request')
29
+ end
30
+
31
+ def signature
32
+ Base64.strict_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, signing_string))
33
+ end
34
+
35
+ conn = Faraday.new(url: API_URL)
36
+
37
+ response = conn.get(uri) do |req|
38
+ req.headers['Authorization'] = "MYRA #{API_KEY}:#{signature}"
39
+ req.headers['Content-Type'] = 'application/json'
40
+ req.headers['Date'] = $date
41
+ end
42
+
43
+ p response.body
metadata ADDED
@@ -0,0 +1,214 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: myra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Florian Kraft
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-07-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.41.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.41.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.12'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.12'
125
+ - !ruby/object:Gem::Dependency
126
+ name: faraday
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.8.11
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.8.11
139
+ - !ruby/object:Gem::Dependency
140
+ name: oj
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.16'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.16'
153
+ description: "\n This gem allows for interacting with the MyraCloud\n API to
154
+ manipulate website and their DNS entries, etc.\n It should take care of hasing
155
+ requests to meet all of the\n requirements set in place by the API. See myracloud.com\n
156
+ \ for more.\n "
157
+ email:
158
+ - florian.kraft@kloeckner.com
159
+ executables: []
160
+ extensions: []
161
+ extra_rdoc_files: []
162
+ files:
163
+ - ".gitignore"
164
+ - ".gitlab-ci.yml"
165
+ - ".rubocop.yml"
166
+ - ".travis.yml"
167
+ - Gemfile
168
+ - Guardfile
169
+ - LICENSE.txt
170
+ - README.md
171
+ - Rakefile
172
+ - bin/console
173
+ - bin/setup
174
+ - lib/myra.rb
175
+ - lib/myra/actions/dns_records.rb
176
+ - lib/myra/actions/domains.rb
177
+ - lib/myra/actions/shared/domain_handler.rb
178
+ - lib/myra/actions/shared/request_handler.rb
179
+ - lib/myra/configuration.rb
180
+ - lib/myra/objects/dns_record.rb
181
+ - lib/myra/objects/domain.rb
182
+ - lib/myra/objects/errors.rb
183
+ - lib/myra/objects/violation.rb
184
+ - lib/myra/request.rb
185
+ - lib/myra/request/http.rb
186
+ - lib/myra/request/signature.rb
187
+ - lib/myra/version.rb
188
+ - myra.gemspec
189
+ - test.rb
190
+ homepage: https://github.com/kloeckner-i/myra
191
+ licenses:
192
+ - MIT
193
+ metadata: {}
194
+ post_install_message:
195
+ rdoc_options: []
196
+ require_paths:
197
+ - lib
198
+ required_ruby_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ required_rubygems_version: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ requirements: []
209
+ rubyforge_project:
210
+ rubygems_version: 2.6.6
211
+ signing_key:
212
+ specification_version: 4
213
+ summary: Gem for interacting with the MyraCloud API
214
+ test_files: []