codily 0.1.0.beta

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,51 @@
1
+ require 'fastly'
2
+ require 'codily/elements/base'
3
+ require 'codily/elements/service'
4
+
5
+ module Codily
6
+ module Elements
7
+ class ServiceBelonggingBase < Base
8
+ def initialize(*)
9
+ super
10
+
11
+ if fastly_obj
12
+ service_version = root.service_version_get(fastly_obj.service_id)
13
+ raise "[bug?] Root#service_version_set should be called before passing fastly obj to ServiceBelonggingBase.new" unless service_version
14
+ @hash[:_service_name] = service_version[:name]
15
+ @hash[:_service_id] = service_version[:id]
16
+ end
17
+ end
18
+
19
+ def self.parent_class
20
+ Service
21
+ end
22
+
23
+ def parent_key
24
+ service_name
25
+ end
26
+
27
+ def service_id
28
+ @hash[:_service_id] ||= begin
29
+ service_version = root.service_version_get(service_name)
30
+ if service_version
31
+ service_version[:id]
32
+ else
33
+ nil
34
+ end
35
+ end
36
+ end
37
+
38
+ def service_name
39
+ @hash[:_service_name]
40
+ end
41
+
42
+ def key
43
+ [service_name, name]
44
+ end
45
+
46
+ def as_hash
47
+ @hash.reject { |k,v| k == :_service_name || k == :_service_id }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ require 'codily/elements/service_belongging_base'
2
+ require 'codily/utils'
3
+
4
+ module Codily
5
+ module Elements
6
+ class Settings < ServiceBelonggingBase
7
+ def setup
8
+ @hash = Utils.symbolize_keys(@hash)
9
+ end
10
+ def dsl_args
11
+ [as_hash]
12
+ end
13
+
14
+ def as_dsl_hash
15
+ {}
16
+ end
17
+
18
+ def key
19
+ service_name
20
+ end
21
+
22
+ def fastly_class
23
+ Fastly::Settings
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require 'codily/elements/service_belongging_base'
2
+ require 'codily/elements/file_loadable'
3
+
4
+ module Codily
5
+ module Elements
6
+ class Vcl < ServiceBelonggingBase
7
+ include FileLoadable
8
+
9
+ def content(obj)
10
+ getset :content, file_loadable(obj)
11
+ end
12
+
13
+ def main(bool = nil)
14
+ getset :main, bool
15
+ end
16
+
17
+ def fastly_class
18
+ Fastly::VCL
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,177 @@
1
+ require 'codily/update_payload'
2
+
3
+ require 'codily/elements/service'
4
+
5
+ require 'codily/elements/condition'
6
+ require 'codily/elements/backend'
7
+ require 'codily/elements/cache_setting'
8
+ require 'codily/elements/dictionary'
9
+ require 'codily/elements/domain'
10
+ require 'codily/elements/gzip'
11
+ require 'codily/elements/header'
12
+ require 'codily/elements/healthcheck'
13
+ require 'codily/elements/request_setting'
14
+ require 'codily/elements/response_object'
15
+ require 'codily/elements/vcl'
16
+ require 'codily/elements/settings'
17
+
18
+ module Codily
19
+ class Engine
20
+ def initialize(fastly, present, desired, service_filter: nil)
21
+ @fastly = fastly
22
+ @present = present
23
+ @desired = desired
24
+
25
+ @service_filter = service_filter
26
+ end
27
+
28
+ attr_reader :fastly, :present, :desired, :service_filter
29
+
30
+ ORDER = [
31
+ Elements::Service,
32
+ Elements::Settings,
33
+ Elements::Condition,
34
+ Elements::Backend,
35
+ Elements::CacheSetting,
36
+ Elements::Dictionary,
37
+ Elements::Domain,
38
+ Elements::Gzip,
39
+ Elements::Header,
40
+ Elements::Healthcheck,
41
+ Elements::RequestSetting,
42
+ Elements::ResponseObject,
43
+ Elements::Vcl,
44
+ ]
45
+
46
+ def run(dry_run: false)
47
+ if dry_run
48
+ puts "(dry-run)"
49
+ puts
50
+ end
51
+
52
+ act_any = false
53
+ act = false
54
+
55
+ creations.each do |new_element|
56
+ act_any = act = true
57
+ puts "CREATE: #{new_element.inspect}"
58
+
59
+ hash = new_element.as_hash
60
+
61
+ if new_element.parent_class == Elements::Service
62
+ service_version = present.service_version_get(new_element.service_name)
63
+ hash[:service_id] = service_version[:id]
64
+ hash[:version] = service_version[:dev]
65
+ end
66
+
67
+ unless dry_run
68
+ new_obj = fastly.create(new_element.fastly_class, hash)
69
+
70
+ if new_element.class == Elements::Service
71
+ present.service_version_set(new_obj.name, new_obj.id, new_obj.versions)
72
+ end
73
+ end
74
+ end
75
+
76
+ puts if act
77
+ act = false
78
+
79
+ updates.each do |present_elem, desired_elem|
80
+ act_any = act = true
81
+ puts "UPDATE: - #{present_elem.inspect}"
82
+ puts " + #{desired_elem.inspect}"
83
+
84
+ payload = UpdatePayload.new(name: present_elem.name, id: present_elem.id, hash: desired_elem.as_hash)
85
+ if desired_elem.parent_class == Elements::Service
86
+ service_version = present.service_version_get(desired_elem.service_name)
87
+ payload.service_id = service_version[:id]
88
+ payload.version_number = service_version[:dev]
89
+ end
90
+
91
+ unless dry_run
92
+ fastly.update(desired_elem.fastly_class, payload)
93
+ end
94
+ end
95
+
96
+ puts if act
97
+ act = false
98
+
99
+ removals.each do |removed_element|
100
+ act_any = act = true
101
+ puts "DELETE: #{removed_element.inspect}"
102
+
103
+ unless dry_run
104
+ removed_element.fastly_obj.delete!
105
+ end
106
+ end
107
+
108
+ unless act_any
109
+ puts "No difference."
110
+ end
111
+ act_any
112
+ end
113
+
114
+ def creations
115
+ new_keys = desired_element_keys - present_element_keys
116
+ sort_elements(new_keys.map{ |_| desired.elements[_[0]][_[1]] })
117
+ end
118
+
119
+ def updates
120
+ common = present_element_keys & desired_element_keys
121
+
122
+ present_existing = common.map{ |_| present.elements[_[0]][_[1]] }
123
+ desired_existing = common.map{ |_| desired.elements[_[0]][_[1]] }
124
+
125
+ raise '!?' if present_existing.size != desired_existing.size
126
+
127
+ present_existing.zip(desired_existing).map do |present_elem, desired_elem|
128
+ if present_elem.as_hash != desired_elem.as_hash
129
+ [present_elem, desired_elem]
130
+ else
131
+ nil
132
+ end
133
+ end.compact.sort_by { |_| ORDER.index(_[0].class) }
134
+ end
135
+
136
+ def removals
137
+ removed_keys = present_element_keys - desired_element_keys
138
+ sort_elements(removed_keys.map{ |_| present.elements[_[0]][_[1]] })
139
+ end
140
+
141
+ private
142
+
143
+ def present_elements
144
+ @present_elements ||= filter_elements(present.all_elements)
145
+ end
146
+
147
+ def desired_elements
148
+ @desired_elements ||= filter_elements(desired.all_elements)
149
+ end
150
+
151
+ def present_element_keys
152
+ @present_element_keys ||= present_elements.map { |_| [_.class, _.key] }
153
+ end
154
+
155
+ def desired_element_keys
156
+ @desired_element_keys ||= desired_elements.map { |_| [_.class, _.key] }
157
+ end
158
+
159
+ def present_service_names
160
+ @present_service_names ||= present.list_element(Elements::Service).each_value.map(&:name)
161
+ end
162
+
163
+ def filter_elements(elems)
164
+ elems.select do |e|
165
+ if e.parent_class == Elements::Service
166
+ (@service_filter ? @service_filter.any? { |_| _ === e.service_name } : true) && present_service_names.include?(e.service_name)
167
+ else
168
+ true
169
+ end
170
+ end
171
+ end
172
+
173
+ def sort_elements(elems)
174
+ elems.sort_by { |_| ORDER.index(_.class) }
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,20 @@
1
+ require 'fastly'
2
+
3
+ class Fastly::RequestSetting
4
+ attr_accessor :timer_support
5
+ end
6
+
7
+ module Codily
8
+ module FastlyExt
9
+ def initialize(opts)
10
+ super
11
+ @opts = opts
12
+ end
13
+
14
+ def client(opts = nil)
15
+ (Thread.current[:fastly_client] ||= {})[self.__id__] ||= Fastly::Client.new(opts || @opts)
16
+ end
17
+ end
18
+ end
19
+
20
+ Fastly.__send__(:prepend, Codily::FastlyExt)
@@ -0,0 +1,77 @@
1
+ require 'fastly'
2
+ require 'codily/fastly_ext'
3
+
4
+ require 'codily/root'
5
+
6
+ require 'codily/elements/service'
7
+
8
+ require 'codily/elements/backend'
9
+ require 'codily/elements/cache_setting'
10
+ require 'codily/elements/condition'
11
+ require 'codily/elements/dictionary'
12
+ require 'codily/elements/domain'
13
+ require 'codily/elements/gzip'
14
+ require 'codily/elements/header'
15
+ require 'codily/elements/healthcheck'
16
+ require 'codily/elements/request_setting'
17
+ require 'codily/elements/response_object'
18
+ require 'codily/elements/vcl'
19
+ require 'codily/elements/settings'
20
+
21
+ module Codily
22
+ class Importer
23
+ def initialize(fastly, import_targets: {}, service_filter: nil, debug: false)
24
+ @fastly = fastly
25
+ @import_targets = import_targets
26
+ @service_filter = service_filter
27
+
28
+ @ran = false
29
+ @root = Codily::Root.new(debug: debug)
30
+ end
31
+
32
+ attr_reader :fastly, :root
33
+
34
+ def run
35
+ return self if @ran
36
+
37
+ fastly.list_services.each do |service|
38
+ if @service_filter
39
+ next unless @service_filter.any? { |_| _ === service.name }
40
+ end
41
+ service_version = root.service_version_set(service.name, service.id, service.versions)
42
+ import_version = @import_targets[service.id] || @import_targets[service.name] || service_version[:dev]
43
+
44
+ root.add_element Elements::Service.new(root, service)
45
+
46
+ threads = {
47
+ Elements::Backend => proc { fastly.list_backends(service_id: service.id, version: import_version) },
48
+ Elements::CacheSetting => proc { fastly.list_cache_settings(service_id: service.id, version: import_version) },
49
+ Elements::Condition => proc { fastly.list_conditions(service_id: service.id, version: import_version) },
50
+ Elements::Dictionary => proc { fastly.list_dictionaries(service_id: service.id, version: import_version) },
51
+ Elements::Domain => proc { fastly.list_domains(service_id: service.id, version: import_version) },
52
+ Elements::Gzip => proc { fastly.list_gzips(service_id: service.id, version: import_version) },
53
+ Elements::Header => proc { fastly.list_headers(service_id: service.id, version: import_version) },
54
+ Elements::Healthcheck => proc { fastly.list_healthchecks(service_id: service.id, version: import_version) },
55
+ Elements::RequestSetting => proc { fastly.list_request_settings(service_id: service.id, version: import_version) },
56
+ Elements::ResponseObject => proc { fastly.list_response_objects(service_id: service.id, version: import_version) },
57
+ Elements::Vcl => proc { fastly.list_vcls(service_id: service.id, version: import_version) },
58
+ Elements::Settings => proc { [fastly.get_settings(service.id, import_version)] },
59
+ }.map do |k, list_proc|
60
+ Thread.new(k) do |klass|
61
+ list_proc.call.map do |_|
62
+ klass.new(root, _)
63
+ end
64
+ end
65
+ end
66
+
67
+ threads.each(&:value)
68
+ threads.flat_map(&:value).each do |elem|
69
+ root.add_element elem
70
+ end
71
+ end
72
+
73
+ @ran = true
74
+ self
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,83 @@
1
+ require 'fastly'
2
+ require 'codily/elements/service'
3
+
4
+ module Codily
5
+ class Root
6
+ class AlreadyDefined < StandardError; end
7
+
8
+ def initialize(debug: false)
9
+ @debug = debug
10
+ @elements = {}
11
+
12
+ @service_versions = {}
13
+ @service_map_name_to_id = {}
14
+ end
15
+
16
+ attr_reader :elements
17
+ attr_accessor :debug
18
+
19
+ def as_hash
20
+ {}
21
+ end
22
+
23
+ def as_dsl_hash
24
+ as_hash
25
+ end
26
+
27
+ # XXX: is it okay having this here?
28
+ def service_version_set(service_name, service_id, versions)
29
+ @service_map_name_to_id[service_name] = service_id
30
+ dev = versions.reverse_each.find { |_| !_.locked }.number
31
+ active = (versions.reverse_each.find(&:active) || versions.reverse_each.find(&:locked)).number
32
+ @service_versions[service_id] = {dev: dev, active: active, name: service_name, id: service_id}
33
+ end
34
+
35
+ def service_version_get(name_or_id)
36
+ @service_versions[@service_map_name_to_id[name_or_id] || name_or_id]
37
+ end
38
+
39
+ def services
40
+ list_element(Elements::Service)
41
+ end
42
+
43
+ def service(name, &block)
44
+ raise AlreadyDefined if services.key?(name)
45
+ add_element(Elements::Service.new(self, {name: name}, &block))
46
+ end
47
+
48
+
49
+ def run_block(&block)
50
+ raise ArgumentError, 'block not given' unless block
51
+
52
+ instance_eval &block
53
+ self
54
+ end
55
+
56
+ def run_string(str, file = '(eval)', line = 1)
57
+ instance_eval str, file, line
58
+ self
59
+ end
60
+
61
+
62
+ def add_element(element)
63
+ h = (@elements[element.class] ||= {})
64
+ raise AlreadyDefined, "#{element.class.name}(#{element.key}) is already defined: (#{h.keys.inspect})" if h.key?(element.key)
65
+ if debug
66
+ puts "DEBUG: #{self.class}/#{'%x' % self.__id__}(add_element): #{element.class}(#{element.key.inspect}) #{element.as_hash.inspect}"
67
+ end
68
+ h[element.key] = element
69
+ end
70
+
71
+ def list_element(klass)
72
+ @elements[klass] ||= {}
73
+ end
74
+
75
+ def find_element(element)
76
+ list_element(element.class)[element.key]
77
+ end
78
+
79
+ def all_elements
80
+ @elements.each_value.flat_map(&:values)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ module Codily
2
+ class UpdatePayload
3
+ def initialize(id: nil, service_id: nil, version_number: nil, name: nil, hash: nil)
4
+ @id = id
5
+ @service_id = service_id
6
+ @version_number = version_number
7
+ @name = name
8
+ @hash = hash
9
+ end
10
+
11
+ attr_accessor :id, :service_id, :version_number, :name, :hash
12
+
13
+ alias version version_number
14
+
15
+ def as_hash
16
+ @hash
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Codily
2
+ module Utils
3
+ def self.symbolize_keys(obj)
4
+ case obj
5
+ when Hash
6
+ Hash[
7
+ obj.map do |k, v|
8
+ [k.to_sym, symbolize_keys(v)]
9
+ end
10
+ ]
11
+ when Array
12
+ obj.map { |_| symbolize_keys(_) }
13
+ else
14
+ obj
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Codily
2
+ VERSION = "0.1.0.beta"
3
+ end
data/lib/codily.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'codily/version'
2
+ require 'codily/cli'
3
+ require 'codily/importer'
4
+ require 'codily/dumper'
5
+ require 'codily/engine'
6
+
data/script/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "codily"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/script/setup ADDED
@@ -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
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codily
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta
5
+ platform: ruby
6
+ authors:
7
+ - Sorah Fukumori
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fastly
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description:
70
+ email:
71
+ - her@sorah.jp
72
+ executables:
73
+ - codily
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bin/codily
85
+ - codily.gemspec
86
+ - lib/codily.rb
87
+ - lib/codily/cli.rb
88
+ - lib/codily/dumper.rb
89
+ - lib/codily/elements/backend.rb
90
+ - lib/codily/elements/base.rb
91
+ - lib/codily/elements/cache_setting.rb
92
+ - lib/codily/elements/condition.rb
93
+ - lib/codily/elements/dictionary.rb
94
+ - lib/codily/elements/domain.rb
95
+ - lib/codily/elements/file_loadable.rb
96
+ - lib/codily/elements/gzip.rb
97
+ - lib/codily/elements/header.rb
98
+ - lib/codily/elements/healthcheck.rb
99
+ - lib/codily/elements/request_setting.rb
100
+ - lib/codily/elements/response_object.rb
101
+ - lib/codily/elements/service.rb
102
+ - lib/codily/elements/service_belongging_base.rb
103
+ - lib/codily/elements/settings.rb
104
+ - lib/codily/elements/vcl.rb
105
+ - lib/codily/engine.rb
106
+ - lib/codily/fastly_ext.rb
107
+ - lib/codily/importer.rb
108
+ - lib/codily/root.rb
109
+ - lib/codily/update_payload.rb
110
+ - lib/codily/utils.rb
111
+ - lib/codily/version.rb
112
+ - script/console
113
+ - script/setup
114
+ homepage: https://github.com/sorah/codily
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.3.1
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.6.4
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Codificate Fastly configuration
138
+ test_files: []