fastlyctl 1.0.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,105 @@
1
+ module FastlyCTL
2
+ class CLI < Thor
3
+ desc "snippet ACTION NAME", "Manipulate snippets on a service. Available actions are create, delete, and list. Use upload command to update snippets."
4
+ method_option :service, :aliases => ["--s"]
5
+ method_option :version, :aliases => ["--v"]
6
+ method_option :type, :aliases => ["--t"]
7
+ method_option :dynamic, :aliases => ["--d"]
8
+ def snippet(action,name=false)
9
+ id = FastlyCTL::Utils.parse_directory unless options[:service]
10
+ id ||= options[:service]
11
+
12
+ abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless id
13
+
14
+ version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
15
+ version ||= options[:version].to_i
16
+
17
+ filename = "#{name}.snippet"
18
+
19
+ case action
20
+ when "upload"
21
+ abort "Must supply a snippet name as second parameter" unless name
22
+
23
+ abort "No snippet file for #{name} found locally" unless File.exists?(filename)
24
+
25
+ active_version = FastlyCTL::Fetcher.get_active_version(id)
26
+
27
+ snippets = FastlyCTL::Fetcher.get_snippets(id, active_version)
28
+
29
+ abort "No snippets found in active version" unless snippets.is_a?(Array) && snippets.length > 0
30
+
31
+ snippet = false
32
+ snippets.each do |s|
33
+ if s["name"] == name
34
+ abort "This command is for dynamic snippets only. Use vcl upload for versioned snippets" if s["dynamic"] == "0"
35
+
36
+ snippet = s
37
+ end
38
+ end
39
+
40
+ abort "No snippet named #{name} found on active version" unless snippet
41
+
42
+ # get the snippet from the dynamic snippet api endpoint so you have the updated content
43
+ snippet = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/snippet/#{snippet["id"]}")
44
+
45
+ new_content = File.read(filename)
46
+
47
+ say(FastlyCTL::Utils.get_diff(snippet["content"],new_content))
48
+
49
+ abort unless yes?("Given the above diff between the old dyanmic snippet content and the new content, are you sure you want to upload your changes? REMEMBER, THIS SNIPPET IS VERSIONLESS AND YOUR CHANGES WILL BE LIVE IMMEDIATELY!")
50
+
51
+ FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/snippet/#{snippet["snippet_id"]}", {:endpoint => :api, body: {
52
+ content: new_content
53
+ }
54
+ })
55
+
56
+ say("New snippet content for #{name} uploaded successfully")
57
+ when "create"
58
+ abort "Must supply a snippet name as second parameter" unless name
59
+
60
+ content = "# Put snippet content here."
61
+
62
+ FastlyCTL::Fetcher.api_request(:post,"/service/#{id}/version/#{version}/snippet",{
63
+ params: {
64
+ name: name,
65
+ type: options[:type] ? options[:type] : "recv",
66
+ content: content,
67
+ dynamic: options.key?(:dynamic) ? 1 : 0
68
+ }
69
+ })
70
+ say("#{name} created on #{id} version #{version}")
71
+
72
+ unless File.exists?(filename)
73
+ File.open(filename, 'w+') {|f| content }
74
+ say("Blank snippet file created locally.")
75
+ return
76
+ end
77
+
78
+ if yes?("Local file #{filename} found. Would you like to upload its content?")
79
+ FastlyCTL::Fetcher.upload_snippet(id,version,File.read(filename),name)
80
+ say("Local snippet file content successfully uploaded.")
81
+ end
82
+ when "delete"
83
+ abort "Must supply a snippet name as second parameter" unless name
84
+
85
+ FastlyCTL::Fetcher.api_request(:delete,"/service/#{id}/version/#{version}/snippet/#{name}")
86
+ say("#{name} deleted on #{id} version #{version}")
87
+
88
+ return unless File.exists?(filename)
89
+
90
+ if yes?("Would you like to delete the local file #{name}.snippet associated with this snippet?")
91
+ File.delete(filename)
92
+ say("Local snippet file #{filename} deleted.")
93
+ end
94
+ when "list"
95
+ snippets = FastlyCTL::Fetcher.api_request(:get,"/service/#{id}/version/#{version}/snippet")
96
+ say("Listing all snippets for #{id} version #{version}")
97
+ snippets.each do |d|
98
+ say("#{d["name"]}: Subroutine: #{d["type"]}, Dynamic: #{d["dynamic"]}")
99
+ end
100
+ else
101
+ abort "#{action} is not a valid command"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,53 @@
1
+ module FastlyCTL
2
+ class CLI < Thor
3
+ desc "token ACTION", "Manipulate API tokens. Available actions are list, create, and delete. Scope defaults to admin:write. Options are --scope and --services. --services should be a comma separated list of services to restrict this token to."
4
+ method_option :customer, :aliases => ["--c"]
5
+ method_option :services, :aliases => ["--s"]
6
+ option :scope
7
+ def token(action)
8
+ case action
9
+ when "list"
10
+ if options[:customer]
11
+ tokens = FastlyCTL::Fetcher.api_request(:get, "/customer/#{options[:customer]}/tokens")
12
+ else
13
+ tokens = FastlyCTL::Fetcher.api_request(:get, "/tokens")
14
+ end
15
+ abort "No tokens to display!" unless tokens.length > 0
16
+
17
+ pp tokens
18
+
19
+ when "create"
20
+ scope = options[:scope]
21
+ scope ||= "global"
22
+
23
+ say("You must login again to create tokens.")
24
+
25
+ login_results = FastlyCTL::Fetcher.login
26
+
27
+ name = ask("What would you like to name your token?")
28
+
29
+ o = {
30
+ user: login_results[:user],
31
+ pass: login_results[:pass],
32
+ code: login_results[:code],
33
+ scope: scope,
34
+ name: name
35
+ }
36
+
37
+ o[:services] = options[:services].split(",") if options[:services]
38
+
39
+ o[:customer] = options[:customer] if options[:customer]
40
+
41
+ resp = FastlyCTL::Fetcher.create_token(o)
42
+
43
+ when "delete"
44
+ id = ask("What is the ID of the token you'd like to delete?")
45
+
46
+ FastlyCTL::Fetcher.api_request(:delete, "/tokens/#{id}", expected_responses: [204])
47
+ say("Token with id #{id} deleted.")
48
+ else
49
+ abort "#{action} is not a valid command"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,155 @@
1
+ module FastlyCTL
2
+ class CLI < Thor
3
+ desc "upload", "Uploads VCL in the current directory to the service."
4
+ method_option :version, :aliases => ["--v"]
5
+ method_option :comment, :aliases => ["--c"]
6
+ def upload
7
+ id = FastlyCTL::Utils.parse_directory
8
+
9
+ abort "Could not parse service id from directory. Use -s <service> to specify, vcl download, then try again." unless id
10
+
11
+ vcls = {}
12
+ snippets = {}
13
+
14
+ Dir.foreach(Dir.pwd) do |p|
15
+ next unless File.file?(p)
16
+ if p =~ /\.vcl$/
17
+ vcls[p.chomp(".vcl")] = {"content" => File.read(p), "name" => p.chomp(".vcl")}
18
+ next
19
+ end
20
+
21
+ if (p =~ /\.snippet$/)
22
+ snippets[p.chomp(".snippet")] = {"content" => File.read(p), "name" => p.chomp(".snippet")}
23
+ end
24
+ end
25
+
26
+ writable_version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
27
+ writable_version ||= options[:version].to_i
28
+ active_version = FastlyCTL::Fetcher.get_active_version(id);
29
+
30
+ old_vcls = FastlyCTL::Fetcher.get_vcl(id, active_version)
31
+ old_snippets = FastlyCTL::Fetcher.get_snippets(id, active_version)
32
+ old_snippets_writable = FastlyCTL::Fetcher.get_snippets(id, writable_version)
33
+
34
+ main_found = false
35
+
36
+ old_vcls ||= {}
37
+ old_vcls.each do |v|
38
+ next unless vcls.has_key? v["name"]
39
+ diff = FastlyCTL::Utils.get_diff(v["content"], vcls[v["name"]]["content"])
40
+
41
+ vcls[v["name"]]["matched"] = true
42
+ vcls[v["name"]]["new"] = false
43
+ main_found = vcls[v["name"]]["main"] = v["main"] == true ? true : false
44
+ vcls[v["name"]]["diff_length"] = diff.length
45
+
46
+ next if diff.length < 2
47
+
48
+ say(diff)
49
+ end
50
+
51
+ old_snippets ||= {}
52
+ old_snippets.each do |s|
53
+ next unless snippets.has_key? s["name"]
54
+ diff = FastlyCTL::Utils.get_diff(s["content"], snippets[s["name"]]["content"])
55
+
56
+ if s["dynamic"] == "1"
57
+ snippets[s["name"]]["skip_because_dynamic"] = true
58
+ next
59
+ end
60
+
61
+ snippets[s["name"]]["matched"] = true
62
+ snippets[s["name"]]["diff_length"] = diff.length
63
+
64
+ next if diff.length < 2
65
+
66
+ say(diff)
67
+ end
68
+ old_snippets_writable ||= {}
69
+ old_snippets_writable.each do |s|
70
+ next unless snippets.has_key? s["name"]
71
+ next if (old_snippets.select {|os| os["name"] == s["name"]}).length > 0
72
+
73
+ if s["dynamic"] == "1"
74
+ snippets[s["name"]]["skip_because_dynamic"] = true
75
+ next
76
+ end
77
+
78
+ snippets[s["name"]]["matched"] = true
79
+ snippets[s["name"]]["diff_length"] = 3
80
+
81
+ say(FastlyCTL::Utils.get_diff("",snippets[s["name"]]["content"]))
82
+ end
83
+
84
+ vcls.delete_if do |k,v|
85
+ if v["name"] == "generated"
86
+ next unless yes?("The name of this file is 'generated.vcl'. Please do not upload generated VCL back to a service. Are you sure you want to upload this file?")
87
+ end
88
+
89
+ if (v["matched"] == true)
90
+ #dont upload if the file isn't different from the old file
91
+ if (v["diff_length"] > 1)
92
+ false
93
+ else
94
+ true
95
+ end
96
+ elsif yes?("VCL #{v["name"]} does not currently exist on the service, would you like to create it?")
97
+ v["new"] = true
98
+ if !main_found
99
+ v["main"] = true
100
+ main_found = true
101
+ end
102
+ say(FastlyCTL::Utils.get_diff("", v["content"]))
103
+ false
104
+ else
105
+ say("Not uploading #{v["name"]}")
106
+ true
107
+ end
108
+ end
109
+
110
+ snippets.delete_if do |k,s|
111
+ if (s["matched"] == true)
112
+ #dont upload if the file isn't different from the old file
113
+ if (s["diff_length"] > 1)
114
+ false
115
+ else
116
+ true
117
+ end
118
+ else
119
+ if s.key?("skip_because_dynamic")
120
+ true
121
+ else
122
+ say("Not uploading #{s["name"]} because it does not exist on the service. Use the \"snippet create\" command to create it.")
123
+ true
124
+ end
125
+ end
126
+ end
127
+
128
+ abort unless yes?("Given the above diff, are you sure you want to upload your changes?")
129
+
130
+ vcls.each do |k,v|
131
+ FastlyCTL::Fetcher.upload_vcl(id, writable_version, v["content"], v["name"], v["main"], v["new"])
132
+
133
+ say("#{v["name"]} uploaded to #{id}")
134
+ end
135
+
136
+ snippets.each do |k,s|
137
+ FastlyCTL::Fetcher.upload_snippet(id, writable_version, s["content"], s["name"])
138
+
139
+ say("#{s["name"]} uploaded to #{id}")
140
+ end
141
+
142
+ if options.key?(:comment)
143
+ FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/version/#{writable_version}",{
144
+ params: {comment: options[:comment]}
145
+ })
146
+ end
147
+
148
+ validation = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{writable_version}/validate")
149
+
150
+ abort "Compiler reported the following error with the generated VCL: #{validation["msg"]}" if validation["status"] == "error"
151
+
152
+ say("VCL(s) have been uploaded to version #{writable_version} and validated.")
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,49 @@
1
+ module FastlyCTL
2
+ class CLI < Thor
3
+ desc "watch POP", "Watch live stats on a service. Optionally specify a POP by airport code."
4
+ method_option :service, :aliases => ["--s"]
5
+ def watch(pop=false)
6
+ service = options[:service]
7
+ service ||= FastlyCTL::Utils.parse_directory
8
+
9
+ abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless service
10
+
11
+ ts = false
12
+
13
+ pop = pop.upcase if pop
14
+
15
+ while true
16
+ data = FastlyCTL::Fetcher.api_request(:get,"/rt/v1/channel/#{service}/ts/#{ts ? ts : 'h/limit/120'}", :endpoint => :app)
17
+
18
+ unless data["Data"].length > 0
19
+ say("No data to display!")
20
+ abort
21
+ end
22
+
23
+ if pop
24
+ unless data["Data"][0]["datacenter"].key?(pop)
25
+ abort "Could not locate #{pop} in data feed."
26
+ end
27
+ agg = data["Data"][0]["datacenter"][pop]
28
+ else
29
+ agg = data["Data"][0]["aggregated"]
30
+ end
31
+
32
+ rps = agg["requests"]
33
+ # gbps
34
+ uncacheable = agg["pass"] + agg["synth"] + agg["errors"]
35
+ bw = ((agg["resp_header_bytes"] + agg["resp_body_bytes"]).to_f * 8.0) / 1000000000.0
36
+ hit_rate = (1.0 - ((agg["miss"] - agg["shield"]).to_f / ((agg["requests"] - uncacheable).to_f))) * 100.0
37
+ passes = agg["pass"]
38
+ miss_time = agg["miss"] > 0 ? ((agg["miss_time"] / agg["miss"]) * 1000).round(0) : 0
39
+ synth = agg["synth"]
40
+ errors = agg["errors"]
41
+
42
+ $stdout.flush
43
+ print " #{rps} req/s | #{bw.round(3)}gb/s | #{hit_rate.round(2)}% Hit Ratio | #{passes} passes/s | #{synth} synths/s | #{miss_time}ms miss time | #{errors} errors/s \r"
44
+
45
+ ts = data["Timestamp"]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,250 @@
1
+ module FastlyCTL
2
+ module Fetcher
3
+ def self.api_request(method, path, options={})
4
+ options[:endpoint] ||= :api
5
+ options[:params] ||= {}
6
+ options[:headers] ||= {}
7
+ options[:body] ||= nil
8
+ options[:force_session] ||= false
9
+ options[:expected_responses] ||= [200]
10
+
11
+ headers = {"Accept" => "application/json", "Connection" => "close", "User-Agent" => "FastlyCTL: https://github.com/fastly/fastlyctl"}
12
+
13
+ if options[:endpoint] == :app
14
+ headers["Referer"] = FastlyCTL::FASTLY_APP
15
+ headers["X-CSRF-Token"] = FastlyCTL::Cookies["fastly.csrf"] if FastlyCTL::Cookies["fastly.csrf"]
16
+ headers["Fastly-API-Request"] = "true"
17
+ end
18
+
19
+ if FastlyCTL::Token && !options[:force_session]
20
+ headers["Fastly-Key"] = FastlyCTL::Token
21
+ else
22
+ headers["Cookie"] = "" if FastlyCTL::Cookies.length > 0
23
+ FastlyCTL::Cookies.each do |k,v|
24
+ headers["Cookie"] << "#{k}=#{v};"
25
+ end
26
+ end
27
+
28
+ headers["Content-Type"] = "application/x-www-form-urlencoded" if (method == :post || method == :put)
29
+
30
+ headers.merge!(options[:headers]) if options[:headers].count > 0
31
+
32
+ # dont allow header splitting on anything
33
+ headers.each do |k,v|
34
+ headers[k] = v.gsub(/\r|\n/,'')
35
+ end
36
+
37
+ url = "#{options[:endpoint] == :api ? FastlyCTL::FASTLY_API : FastlyCTL::FASTLY_APP}#{path}"
38
+
39
+ response = Typhoeus::Request.new(
40
+ url,
41
+ method: method,
42
+ params: options[:params],
43
+ headers: headers,
44
+ body: options[:body]
45
+ ).run
46
+
47
+ if options[:expected_responses].include?(response.response_code)
48
+ if response.headers["Set-Cookie"]
49
+ response.headers["Set-Cookie"] = [response.headers["Set-Cookie"]] if response.headers["Set-Cookie"].is_a? String
50
+ response.headers["Set-Cookie"].each do |c|
51
+ name, value = c.match(/^([^=]*)=([^;]*).*/i).captures
52
+ FastlyCTL::Cookies[name] = value
53
+ end
54
+ end
55
+ else
56
+ case response.response_code
57
+ when 400
58
+ error = "400: Bad API request--got bad request response."
59
+ when 403
60
+ error = "403: Access Denied by API. Run login command to authenticate."
61
+ when 404
62
+ error = "404: Service does not exist or bad path requested."
63
+ when 503
64
+ error = "503: API is offline."
65
+ else
66
+ error = "API responded with status #{response.response_code}."
67
+ end
68
+
69
+ error += " Method: #{method.to_s.upcase}, Path: #{path}\n"
70
+ error += "Message from API: #{response.response_body}"
71
+
72
+ abort error
73
+ end
74
+
75
+ return response.response_body if (response.headers["Content-Type"] != "application/json")
76
+
77
+ if response.response_body.length > 1
78
+ begin
79
+ return JSON.parse(response.response_body)
80
+ rescue JSON::ParserError
81
+ abort "Failed to parse JSON response from Fastly API"
82
+ end
83
+ else
84
+ return {}
85
+ end
86
+ end
87
+
88
+ def self.domain_to_service_id(domain)
89
+ response = Typhoeus::Request.new(FastlyCTL::FASTLY_APP, method:"FASTLYSERVICEMATCH", headers: { :host => domain}).run
90
+
91
+ abort "Failed to fetch Fastly service ID or service ID does not exist" if response.response_code != 204
92
+
93
+ abort "Fastly response did not contain service ID" unless response.headers["Fastly-Service-Id"]
94
+
95
+ return response.headers["Fastly-Service-Id"]
96
+ end
97
+
98
+ def self.get_active_version(id)
99
+ service = self.api_request(:get, "/service/#{id}")
100
+
101
+ max = 1
102
+
103
+ service["versions"].each do |v|
104
+ if v["active"] == true
105
+ return v["number"]
106
+ end
107
+
108
+ max = v["number"] if v["number"] > max
109
+ end
110
+
111
+ return max
112
+ end
113
+
114
+ def self.get_writable_version(id)
115
+ service = self.api_request(:get, "/service/#{id}")
116
+
117
+ active = false
118
+ version = false
119
+ max = 1
120
+ service["versions"].each do |v|
121
+ if v["active"] == true
122
+ active = v["number"].to_i
123
+ end
124
+
125
+ if active && v["number"].to_i > active && v["locked"] == false
126
+ version = v["number"]
127
+ end
128
+
129
+ max = version if version && version > max
130
+ end
131
+
132
+ return max unless active
133
+
134
+ version = self.api_request(:put, "/service/#{id}/version/#{active}/clone")["number"] unless version
135
+
136
+ return version
137
+ end
138
+
139
+ def self.get_vcl(id, version, generated=false)
140
+ if generated
141
+ vcl = self.api_request(:get, "/service/#{id}/version/#{version}/generated_vcl")
142
+ else
143
+ vcl = self.api_request(:get, "/service/#{id}/version/#{version}/vcl?include_content=1")
144
+ end
145
+
146
+ if vcl.length == 0
147
+ return false
148
+ else
149
+ return vcl
150
+ end
151
+ end
152
+
153
+ def self.get_snippets(id,version)
154
+ snippet = self.api_request(:get, "/service/#{id}/version/#{version}/snippet")
155
+
156
+ if snippet.length == 0
157
+ return false
158
+ else
159
+ return snippet
160
+ end
161
+ end
162
+
163
+ def self.upload_snippet(service,version,content,name)
164
+ return FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/snippet/#{name}", {:endpoint => :api, body: {
165
+ content: content
166
+ }
167
+ })
168
+ end
169
+
170
+ def self.upload_vcl(service,version,content,name,is_main=true,is_new=false)
171
+ params = { name: name, main: "#{is_main ? "1" : "0"}", content: content }
172
+
173
+ # try to create, if that fails, update
174
+ if is_new
175
+ response = FastlyCTL::Fetcher.api_request(:post, "/service/#{service}/version/#{version}/vcl", {:endpoint => :api, body: params, expected_responses:[200,409]})
176
+ if response["msg"] != "Duplicate record"
177
+ return
178
+ end
179
+ end
180
+
181
+ response = FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/vcl/#{name}", {:endpoint => :api, body: params, expected_responses: [200,404]})
182
+
183
+ # The VCL got deleted so recreate it.
184
+ if response["msg"] == "Record not found"
185
+ FastlyCTL::Fetcher.api_request(:post, "/service/#{service}/version/#{version}/vcl", {:endpoint => :api, body: params})
186
+ end
187
+ end
188
+
189
+ def self.login
190
+ thor = Thor::Shell::Basic.new
191
+
192
+ user = thor.ask("Username: ")
193
+ pass = thor.ask("Password: ", :echo => false)
194
+
195
+ resp = FastlyCTL::Fetcher.api_request(:post, "/login", { :endpoint => :app, params: { user: user, password: pass}})
196
+
197
+ if resp["needs_two_factor_auth"]
198
+ two_factor = true
199
+
200
+ thor.say("\nTwo factor auth enabled on account, second factor needed.")
201
+ code = thor.ask('Please enter verification code:', echo: false)
202
+
203
+ resp = FastlyCTL::Fetcher.api_request(:post, "/two_factor_auth/verify", {force_session: true, :endpoint => :app, params: { token: code }} )
204
+ else
205
+ thor.say("\nTwo factor auth is NOT enabled. You should go do that immediately.")
206
+ end
207
+
208
+ thor.say("Login successful!")
209
+
210
+ return { user: user, pass: pass, two_factor: two_factor, code: code }
211
+ end
212
+
213
+ def self.create_token(options)
214
+ thor = Thor::Shell::Basic.new
215
+
216
+ headers = {}
217
+ headers["Fastly-OTP"] = options[:code] if options[:code]
218
+
219
+ FastlyCTL::Fetcher.api_request(:post, "/sudo", {
220
+ force_session: true,
221
+ endpoint: :api,
222
+ params: {
223
+ user: options[:user],
224
+ password: options[:pass]
225
+ },
226
+ headers: headers
227
+ })
228
+
229
+ params = {
230
+ name: options[:name],
231
+ scope: options[:scope],
232
+ user: options[:user],
233
+ password: options[:pass]
234
+ }
235
+
236
+ params[:services] = options[:services] if options[:services]
237
+
238
+ resp = FastlyCTL::Fetcher.api_request(:post, "/tokens", {
239
+ force_session: true,
240
+ endpoint: :api,
241
+ params: params,
242
+ headers: headers
243
+ })
244
+
245
+ thor.say("\n#{resp["id"]} created.")
246
+
247
+ return resp
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,72 @@
1
+ module FastlyCTL
2
+ module Utils
3
+ def self.open_service(id)
4
+ Launchy.open(FastlyCTL::FASTLY_APP + FastlyCTL::TANGO_PATH + id)
5
+ end
6
+
7
+ def self.parse_directory(path=false)
8
+ directory = Dir.pwd unless path
9
+ directory = path if path
10
+
11
+ id = directory.match(/.* \- ([^\-]*)$/i)
12
+ id = id == nil ? false : id.captures[0]
13
+
14
+ return id
15
+ end
16
+
17
+ def self.parse_name(path=false)
18
+ directory = Dir.pwd unless path
19
+ directory = path if path
20
+
21
+ name = directory.match(/(.*) \- [^\-]*$/i)
22
+ name = name == nil ? false : name.captures[0]
23
+
24
+ return name
25
+ end
26
+
27
+ def self.get_diff(old_vcl,new_vcl)
28
+ options = {
29
+ include_diff_info: true,
30
+ diff: ["-E", "-p"],
31
+ context: 3
32
+ }
33
+ return Diffy::Diff.new(old_vcl, new_vcl, options).to_s(:color)
34
+ end
35
+
36
+ def self.diff_generated(v1,v2)
37
+ diff = ""
38
+
39
+ diff << "\n" + self.get_diff(v1["content"], v2["content"])
40
+
41
+ return diff
42
+ end
43
+
44
+ def self.diff_versions(v1,v2)
45
+ diff = ""
46
+ v1 ||= Array.new
47
+ v2 ||= Array.new
48
+
49
+ v1.each do |vcl1|
50
+ v2_content = false
51
+
52
+ v2.each do |vcl2|
53
+ v2_content = vcl2["content"] if (vcl1["name"] == vcl2["name"])
54
+ if (v2_content)
55
+ vcl2["matched"] = true
56
+ break
57
+ end
58
+ end
59
+
60
+ v2_content = "" unless v2_content
61
+
62
+ diff << "\n" + self.get_diff(vcl1["content"], v2_content)
63
+ end
64
+
65
+ v2.each do |vcl|
66
+ diff << "\n" + self.get_diff("", vcl["content"]) if !(vcl.has_key? "matched")
67
+ end
68
+
69
+ return diff
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module FastlyCTL
2
+ VERSION = "1.0.1"
3
+ end