Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .chamber.pub.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Gg5nTM/XOIuivTcgfKH
w+wMHnP6bGKEPNxH68FBfIH5p8cShuCRaKDJw9q/tXwd5C0wRVNXuRnqSu/Rj977
Z4SnjmAsW9rYWbT6mDsAzCcurY21QjRd37oIddLBWApYv/8gCnut3mN3X4V88HOF
tVthj1x36ACPse5DnmLxnmPZRgtuIrWICpzVrlNUSgTDbo8u/KjRPlTiko8n7gDy
tOA7mVfOG1YUZA4n8FBzeINSUup4b0knpAZKkibamra7fqWxzVDOan++aqx+aubW
IScgTj/rbYMG2IMAF0hkEDLUiP860znBerbVXcCAdA0kUWAdAw12BsbT/51/tmvH
VwIDAQAB
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
.rbenv-gemsets
bin
o-rdoc

# Private and protected key files for Chamber
.chamber.pem
.chamber.pem.enc
2 changes: 2 additions & 0 deletions bin/setup-and-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ das_setup() {

gem install excon -v 0.57.1
gem install awesome_print -v 1.8.0
gem install chamber -v 2.10.1
gem install prolog-dry_types -v 0.3.4
gem install semantic_logger -v 4.1.1
gem install terminal-table -v 1.8.0
gem install thor -v 0.19.4
Expand Down
7 changes: 5 additions & 2 deletions do_api_scripting.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency "chamber", '2.10.1'
spec.add_dependency "dry-struct", '0.3.1'
spec.add_dependency "excon", "0.57.1"
spec.add_dependency "prolog-dry_types", '0.3.4'
spec.add_dependency "semantic_logger", '4.1.1'
spec.add_dependency "terminal-table", '1.8.0'
spec.add_dependency 'thor', '0.19.4'

spec.add_development_dependency "bundler", "1.15.1"
spec.add_development_dependency "bundler", "1.15.3"
spec.add_development_dependency "rake", "12.0.0"
spec.add_development_dependency "minitest", "5.10.2"
spec.add_development_dependency "minitest", "5.10.3"

spec.add_development_dependency "minitest-matchers", '1.4.1'
spec.add_development_dependency "minitest-reporters", '1.1.14'
Expand Down
33 changes: 33 additions & 0 deletions exe/settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 2017/07/25
# ----------
#
# To decrypt and use this data, you *must* first decrypt the .chamber.pem.enc
# file for this Git repository using the "passphrase" you have been given
# securely out-of-band (which was generated by the command sequence
# `chamber secure -f ./exe/settings.yml`). To decrypt the .pem.enc file for use
# with Chamber, run:
#
# $ cp /path/to/{.chamber.pem.enc,.chamber.pem}
# $ ssh-keygen -p -f /path/to/.chamber.pem
#
# Enter the passphrase when prompted and leave the new passphrase blank.
#
# If you want to change the value of the stored API token, set the value for
# `_secure_api_token` to your new desired value and then run `chamber secure` as
# described above. Once secured, this file is safe for committal to a Git public
# repository. **DO NOT** commit `.chamber.pem`, and it's properly paranoid to
# only exchange the putatively-secure `.chamber.pem.enc` file securely. The
# `chamber.pub.pem` file is safe for committal.
#
# As a reminder, to override this value from the command line, run
#
# $ DO_API_TOKEN=your-own-token do_api ...
#
# (or `DO_API_TOKEN=your-own-token bundle exec do_api ...` in developer mode).
# You **MUST** use the explicit environment variable if you do not have the
# `.chamber.pem` file!
#

do:
_secure_api_token: NdJMO7AzqhP3kADCxeChf8I7lk0bLYg/lwHw/F7v8QrdPlyLfXWAUz4hhDFiXt5dHqVUA50nrsEBxOqjNQE71O0hHkexI/j9wbqlWfbbgQoPoilO4CzzHkyj71sqXxyav/QLjn3B0lf43/SD3JUE3QnpbzsskYrdC9d5v7jGzFbk3MshpRZ94229NwC6eXLcZaTQEEPqz12reW96duJHmo9DvikB3IFgrmLURlrjVNg2abYpu3KooHbHMxEW/c+7fuwYnXnBrSentuehytPTY6z9g7SWmXikJNy1K56BF55m7YrWK9gsZC2l7zflBr8DIDf7GKPKviaELLQF+nBxAw==
# _secure_api_token: This is a dummy value. Override with the DO_API_TOKEN environment variable if you have no .chamber.pem file.
99 changes: 99 additions & 0 deletions lib/do_api_scripting/api/all_droplets.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

require 'excon'
require 'prolog/dry_types'

require_relative './droplet_list'
require_relative './all_droplets/data_request'
require_relative './all_droplets/stubs'

# Code to support scripting the DigitalOcean API, e.g., for use with Ansible.
module DoApiScripting
module API
# Class uses DO API to retrieve list of all Droplets owned by user
class AllDroplets
def self.call(request_module: DEFAULT_REQUEST_MODULE)
new(request_module).call
end

def call
return_obj
end

protected

def initialize(request_module)
@request_module = request_module
self
end

private

attr_reader :request_module

DEFAULT_REQUEST_MODULE = API::AllDroplets::DataRequest::NonStubs
private_constant :DEFAULT_REQUEST_MODULE

DUMMY_DATA = {
meta: { total: 3 },
links: {},
droplets: [
{
created_at: '2017-06-21T08:20:42Z',
id: '52434906',
name: 'suirdemo1-test',
networks: {
v4: [
{ type: 'private', ip_address: '10.130.10.113' },
{ type: 'public', ip_address: '128.199.105.180' }
],
v6: []
},
region: { name: 'Singapore 1', slug: 'sgp1' },
size_slug: '512mb',
status: 'active'
},
{
created_at: '2017-06-22T10:07:34Z',
id: '52569259',
name: 'suirdemo2',
networks: {
v4: [
{ type: 'private', ip_address: '10.130.19.125' },
{ type: 'public', ip_address: '128.199.73.100' }
],
v6: []
},
region: { name: 'Singapore 1', slug: 'sgp1' },
size_slug: '512mb',
status: 'active'
}
]
}.freeze
private_constant :DUMMY_DATA

# Reek says this is a :reek:UtilityFunction.
def droplet_data
data = DataRequest.get(request_module: request_module)
droplets = data.body[:droplets].map do |datum|
droplet_from_datum(datum)
end
{ droplets: droplets, status: data.status }
end

# Reek sees this as a :reek:UtilityFunction. We'll get around to it.
def droplet_from_datum(datum)
intermediate = datum.reject do |attrib, _|
%i[networks region].include?(attrib)
end
intermediate[:public_ip] = datum.dig(:networks, :v4, 1, :ip_address)
intermediate[:region_name] = datum.dig(:region, :name)
DropletInfo.new intermediate
end

def return_obj
DropletList.new droplet_data.to_h
end
end # class DoApiScripting::API::AllDroplets
end
end
67 changes: 67 additions & 0 deletions lib/do_api_scripting/api/all_droplets/data_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

require 'chamber'
require 'excon'
require 'json'

require_relative './non_stubs'

# This **must** be done before any Chamber environment is accessed. Repeated
# calls to `Chamber.load` apparently (and logically) are harmless, only adding a
# performance penalty in the event of large Chamber config files being parsed.

Chamber.load basepath: "#{ENV['PWD']}/exe"

# Code to support scripting the DigitalOcean API, e.g., for use with Ansible.
module DoApiScripting
module API
# Class uses DO API to retrieve list of all Droplets owned by user
class AllDroplets
# Class encapsulating sending request to/receiving response from DO API.
class DataRequest
def self.get(auth_header: :default, request_module: NonStubs)
new(auth_header, request_module).get
end

def get
Struct.new(:status, :body).new response.status, body
end

protected

def initialize(auth_header_in, stub_module)
@stubs = stub_module
@auth_header = auth_header_in
@auth_header = DEFAULT_AUTH_HEADER if auth_header_in == :default
@response = nil
self
end

private

attr_reader :auth_header, :stubs

DEFAULT_AUTH_HEADER = "Bearer #{Chamber.env.do.api_token}"
private_constant :DEFAULT_AUTH_HEADER

URL = 'https://api.digitalocean.com/v2/droplets'
private_constant :URL

def body
JSON.parse(response.body, symbolize_names: true)
end

def headers
{
'Content-Type': 'application/json',
'Authorization': auth_header
}
end

def response
@response ||= stubs.request(headers: headers, url: URL)
end
end # class DoApiScripting::API::AllDroplets::DataRequest
end # class DoApiScripting::API::AllDroplets
end
end
21 changes: 21 additions & 0 deletions lib/do_api_scripting/api/all_droplets/non_stubs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require 'excon'

# Code to support scripting the DigitalOcean API, e.g., for use with Ansible.
module DoApiScripting
module API
# Class uses DO API to retrieve list of all Droplets owned by user
class AllDroplets
# Class encapsulating sending request to/receiving response from DO API.
class DataRequest
# Make the API request without applying stubs.
module NonStubs
def self.request(headers:, url:)
Excon.get(url, headers: headers)
end
end
end # class DoApiScripting::API::AllDroplets::DataRequest
end # class DoApiScripting::API::AllDroplets
end
end
74 changes: 74 additions & 0 deletions lib/do_api_scripting/api/all_droplets/stubs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

require 'excon'

# Code to support scripting the DigitalOcean API, e.g., for use with Ansible.
module DoApiScripting
module API
# Class uses DO API to retrieve list of all Droplets owned by user
class AllDroplets
# Class encapsulating sending request to/receiving response from DO API.
class DataRequest
# Stubbing data shouldn't be part of the core logic, should it? :P
module Stubs
def self.request(headers:, url:)
on
resp = Excon.get(url, request_params(headers))
off
resp
end

def self.off
Excon.stubs.clear
end

def self.on
Excon.stub({}, status: 200, body: JSON.dump(DUMMY_DATA))
end

def self.request_params(headers)
{ mock: true, headers: headers }
end

DUMMY_DATA = {
meta: { total: 3 },
links: {},
droplets: [
{
created_at: '2017-06-21T08:20:42Z',
id: '52434906',
name: 'suirdemo1-test',
networks: {
v4: [
{ type: 'private', ip_address: '10.130.10.113' },
{ type: 'public', ip_address: '128.199.105.180' }
],
v6: []
},
region: { name: 'Singapore 1', slug: 'sgp1' },
size_slug: '512mb',
status: 'active'
},
{
created_at: '2017-06-22T10:07:34Z',
id: '52569259',
name: 'suirdemo2',
networks: {
v4: [
{ type: 'private', ip_address: '10.130.19.125' },
{ type: 'public', ip_address: '128.199.73.100' }
],
v6: []
},
region: { name: 'Singapore 1', slug: 'sgp1' },
size_slug: '512mb',
status: 'active'
}
]
}.freeze
private_constant :DUMMY_DATA
end
end # class DoApiScripting::API::AllDroplets::DataRequest
end # class DoApiScripting::API::AllDroplets
end
end
19 changes: 19 additions & 0 deletions lib/do_api_scripting/api/droplet_info.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'prolog/dry_types'

# Code to support scripting the DigitalOcean API, e.g., for use with Ansible.
module DoApiScripting
module API
# Attributes of a DO Droplet to be reported on via our API.
class DropletInfo < Dry::Struct::Value
attribute :id, Types::Coercible::Int
attribute :name, Types::Strict::String
attribute :created_at, Types::Json::Time
attribute :public_ip, Types::Strict::String # custom type?
attribute :region_name, Types::Strict::String
attribute :size_slug, Types::Strict::String
attribute :status, Types::Strict::String
end # class DoApiScripting::API::DropletInfo
end
end
16 changes: 16 additions & 0 deletions lib/do_api_scripting/api/droplet_list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require 'prolog/dry_types'

require_relative './droplet_info'

# Code to support scripting the DigitalOcean API, e.g., for use with Ansible.
module DoApiScripting
module API
# Container class returning information about Droplets from API.
class DropletList < Dry::Struct::Value
attribute :droplets, Types::Strict::Array.member(DropletInfo)
attribute :status, Types::Strict::Int
end # class DoApiScripting::API::DropletList
end
end
Loading