fastlyctl 1.0.15 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d56f375e7f8e4c2105efabf9b224428666fe432702c0a259a6da21d816409a3
4
- data.tar.gz: b509f52f29c1a5dbb5a5d5f39ad42aa3f0c8701835c368d810d119487b64a665
3
+ metadata.gz: 4f7ad45346735be1f793e4975d14f31c2ab9b6df7867c2456a5e18ed1547653d
4
+ data.tar.gz: c5f00dec1bf47c871bc207526bd09df6d185ee3469c383bc18c689493372b464
5
5
  SHA512:
6
- metadata.gz: cf19cda3f417d5063fd29729ed80c10688cb9d9eb3e93ce16409c3a06d02dc641495710d38a085922d059ed5292e5713724a9769d2af75f773227eaece4c3c2c
7
- data.tar.gz: 4192c6bec0a62011d77d7e0e25362a55563ac7ec8a65e4278a23715e2a75abd031a755091fb51749b127e6f2998a6a20c3255a925553f3981de803164aa77a8f
6
+ metadata.gz: d5b073b0127e3ad3d5e386f7487492fe81ad3be57420ce9dd0dd253de76374e5ccb18916ec6f00fee3e64f9da59509a0e964e13c5dd6fcfa2d759fe9c73134b1
7
+ data.tar.gz: 8565d2fcbd646b7f8a4fc194e836e3bf0520c2338ebd43497ea23e2579222abe27a3b84df3f0794108f4531b35e318baff7740a03cc4a31d908448513bd71847
@@ -0,0 +1,4 @@
1
+ ---
2
+ name: Tracking issue
3
+ about: DEPRECATED: FastlyCTL is no longer maintained or supported. Many features are already available in the official Fastly CLI (https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible.
4
+ ---
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # FastlyCTL [![Gem Version](https://img.shields.io/gem/v/fastlyctl.svg)](https://rubygems.org/gems/fastlyctl)
2
2
 
3
+ **DEPRECATED**: FastlyCTL is no longer maintained or supported. Many features are already available in the official [Fastly CLI](https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible.
4
+
5
+ ---
6
+
3
7
  A CLI for managing Fastly configurations with [Fastly's API](https://docs.fastly.com/api/config).
4
8
 
5
9
  ## Dependencies
@@ -321,6 +325,22 @@ Flags:
321
325
  * --d: When used with the create command, specifies that the snippet should be dynamic.
322
326
  * --y: Answer yes to all prompts
323
327
 
328
+ ### tls
329
+
330
+ #### managed
331
+
332
+ Usage:
333
+
334
+ ```
335
+ fastlyctl tls managed [subcommand] [domain]
336
+ ```
337
+
338
+ Available Subcommands:
339
+ * create: Create a Managed TLS Subscription for `[domain]`
340
+ * status: Print the status of all Managed TLS Subscriptions
341
+ * challenges: Print the challenges available for the verification of a certificate for `[domain]`
342
+ * delete: Delete a Managed TLS Subscription for `[domain]`
343
+
324
344
  ### token
325
345
 
326
346
  Manipulate tokens for an account.
@@ -371,11 +391,15 @@ Flags:
371
391
 
372
392
  The `--debug` flag is available on any command. Using it will cause fastlyctl to print the libcurl output for any requests it makes.
373
393
 
374
- ## Contributing
375
-
376
- Submit a pull request. Don't break anything.
377
-
378
394
  ## License
379
395
 
380
396
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
381
397
 
398
+ ## Publishing
399
+
400
+ Bump version in `/lib/fastlyctl/version.rb`.
401
+
402
+ ```bash
403
+ $ gem build fastlyctl.gemspec
404
+ $ gem push fastlyctl-1.0.X.gem
405
+ ```
data/fastlyctl.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["stephen@fastly.com"]
11
11
 
12
12
  spec.summary = %q{CLI tool for interacting with the Fastly API}
13
- spec.description = %q{This gem provides a CLI for managing Fastly configurations}
13
+ spec.description = %q{DEPRECATED: FastlyCTL is no longer maintained or supported. Many features are already available in the official Fastly CLI (https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible.}
14
14
  spec.homepage = "http://www.github.com/fastly/fastlyctl"
15
15
  spec.license = "MIT"
16
16
 
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency "thor", "~> 0.19.4"
35
35
  spec.add_runtime_dependency 'diffy', '~> 3.2.1'
36
36
  spec.add_runtime_dependency 'launchy', '~> 2.4.3', '>= 2.4.3'
37
+ spec.add_runtime_dependency 'openssl', '~> 2.1.2', '>= 2.1.2'
37
38
  end
data/lib/fastlyctl.rb CHANGED
@@ -6,23 +6,23 @@ require "uri"
6
6
  require "launchy"
7
7
  require "erb"
8
8
  require "pp"
9
+ require "openssl"
9
10
 
10
11
  require "fastlyctl/version"
11
12
  require "fastlyctl/fetcher"
12
13
  require "fastlyctl/clone_utils"
13
14
  require "fastlyctl/utils"
15
+ require "fastlyctl/subcommand_patch"
14
16
  require "fastlyctl/cli"
15
17
 
16
18
  include ERB::Util
17
19
 
18
20
  module FastlyCTL
19
- COOKIE_JAR = ENV['HOME'] + "/.fastlyctl_cookie_jar"
20
21
  TOKEN_FILE = ENV['HOME'] + "/.fastlyctl_token"
21
22
  FASTLY_API = "https://api.fastly.com"
22
- FASTLY_APP = "https://manage.fastly.com"
23
+ FASTLY_RT_API = "https://rt.fastly.com"
23
24
  TANGO_PATH = "/configure/services/"
24
25
 
25
- Cookies = File.exist?(FastlyCTL::COOKIE_JAR) ? JSON.parse(File.read(FastlyCTL::COOKIE_JAR)) : {}
26
26
  # Don't allow header splitting with the key
27
- Token = File.exist?(FastlyCTL::TOKEN_FILE) ? File.read(FastlyCTL::TOKEN_FILE) : false
27
+ Token = File.exist?(FastlyCTL::TOKEN_FILE) ? File.read(FastlyCTL::TOKEN_FILE) : (ENV['FASTLYCLI_TOKEN'] ? ENV['FASTLYCLI_TOKEN'] : false)
28
28
  end
data/lib/fastlyctl/cli.rb CHANGED
@@ -17,6 +17,7 @@ require "fastlyctl/commands/acl"
17
17
  require "fastlyctl/commands/copy"
18
18
  require "fastlyctl/commands/logging"
19
19
  require "fastlyctl/commands/condition"
20
+ require "fastlyctl/commands/tls"
20
21
 
21
22
 
22
23
  module FastlyCTL
@@ -24,7 +25,9 @@ module FastlyCTL
24
25
  class_option :debug, :desc => 'Enabled debug mode output'
25
26
 
26
27
  def initialize(a,b,c)
27
- unless File.exist?(FastlyCTL::TOKEN_FILE)
28
+ warn "DEPRECATED: FastlyCTL is no longer maintained or supported. Many features are already available in the official Fastly CLI (https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible."
29
+
30
+ unless File.exist?(FastlyCTL::TOKEN_FILE) || ENV['FASTLYCLI_TOKEN']
28
31
  if yes?("Unable to locate API token. Would you like to login first?")
29
32
  self.login
30
33
  end
@@ -59,12 +59,12 @@ module FastlyCTL
59
59
 
60
60
  if main === true
61
61
  # the "main-ness" of the vcl does not get carried over during creation. must explicitly set main
62
- FastlyCTL::Fetcher.api_request(:put, "/service/#{sid}/version/#{version}/vcl/#{URI.escape(obj["name"])}/main")
62
+ FastlyCTL::Fetcher.api_request(:put, "/service/#{sid}/version/#{version}/vcl/#{FastlyCTL::Utils.percent_encode(obj["name"])}/main")
63
63
  end
64
64
 
65
65
  if type == "director"
66
66
  backends.each do |b|
67
- FastlyCTL::Fetcher.api_request(:post, "/service/#{sid}/version/#{version}/director/#{URI.escape(obj["name"])}/backend/#{b}", body: obj )
67
+ FastlyCTL::Fetcher.api_request(:post, "/service/#{sid}/version/#{version}/director/#{FastlyCTL::Utils.percent_encode(obj["name"])}/backend/#{b}", body: obj )
68
68
  end
69
69
  end
70
70
 
@@ -20,7 +20,7 @@ module FastlyCTL
20
20
  version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
21
21
  version ||= options[:version]
22
22
 
23
- encoded_name = URI.escape(name) if name
23
+ encoded_name = FastlyCTL::Utils.percent_encode(name) if name
24
24
 
25
25
  case action
26
26
  when "create"
@@ -32,7 +32,7 @@ module FastlyCTL
32
32
  version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
33
33
  version ||= options[:version].to_i
34
34
 
35
- encoded_name = URI.escape(name) if name
35
+ encoded_name = FastlyCTL::Utils.percent_encode(name) if name
36
36
 
37
37
  case action
38
38
  when "list"
@@ -19,7 +19,7 @@ module FastlyCTL
19
19
  path += "/#{obj_name}" unless obj_type == "settings"
20
20
  obj = FastlyCTL::Fetcher.api_request(:get, path)
21
21
 
22
- encoded_name = URI.escape(obj_name)
22
+ encoded_name = FastlyCTL::Utils.percent_encode(obj_name)
23
23
 
24
24
  if (obj_type == "settings")
25
25
  puts "Copying settings from #{id} version #{source_version} to #{target_id} version #{target_version}..."
@@ -20,7 +20,7 @@ module FastlyCTL
20
20
  version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
21
21
  version ||= options[:version]
22
22
 
23
- encoded_name = URI.escape(name) if name
23
+ encoded_name = FastlyCTL::Utils.percent_encode(name) if name
24
24
 
25
25
  case action
26
26
  when "create"
@@ -49,14 +49,14 @@ module FastlyCTL
49
49
  abort "Must specify name for dictionary" unless name
50
50
  abort "Must specify key and value for dictionary item" unless (key && value)
51
51
  dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{encoded_name}")
52
- FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/dictionary/#{dict["id"]}/item/#{key}", params: { item_value: value })
52
+ FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/dictionary/#{dict["id"]}/item/#{FastlyCTL::Utils.percent_encode(key)}", params: { item_value: value })
53
53
 
54
54
  say("Dictionary item #{key} set to #{value}.")
55
55
  when "remove"
56
56
  abort "Must specify name for dictionary" unless name
57
57
  abort "Must specify key for dictionary item" unless key
58
58
  dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{encoded_name}")
59
- FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/dictionary/#{dict["id"]}/item/#{key}")
59
+ FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/dictionary/#{dict["id"]}/item/#{FastlyCTL::Utils.percent_encode(key)}")
60
60
 
61
61
  say("Item #{key} removed from dictionary #{name}.")
62
62
  when "list_items"
@@ -13,23 +13,21 @@ module FastlyCTL
13
13
 
14
14
  say("Proceeding with username/password login...")
15
15
 
16
- login_results = FastlyCTL::Fetcher.login
16
+ username = ask("Username: ")
17
+ password = ask("Password: ", :echo => false)
18
+ say("")
17
19
 
18
- File.open(FastlyCTL::COOKIE_JAR , 'w+') {|f| f.write(JSON.dump(FastlyCTL::Cookies)) }
19
- File.chmod(0600, FastlyCTL::COOKIE_JAR)
20
-
21
- say("Creating root scoped token...")
22
-
23
- if login_results[:user].include?("@fastly.com") && !login_results[:user].include?("+")
20
+ if username.include?("@fastly.com") && !username.include?("+")
24
21
  scope = "root"
25
22
  else
26
23
  scope = "global"
27
24
  end
28
25
 
26
+ say("Creating #{scope} scope token...")
27
+
29
28
  o = {
30
- user: login_results[:user],
31
- pass: login_results[:pass],
32
- code: login_results[:code],
29
+ username: username,
30
+ password: password,
33
31
  scope: scope,
34
32
  name: "fastlyctl_token"
35
33
  }
@@ -3,11 +3,10 @@ module FastlyCTL
3
3
  desc "purge_all", "Purge all content from a service."
4
4
  method_option :service, :aliases => ["--s"]
5
5
  def purge_all
6
- parsed_id = FastlyCTL::Utils.parse_directory
6
+ id = FastlyCTL::Utils.parse_directory unless options[:service]
7
+ id ||= options[:service]
7
8
 
8
- id = FastlyCTL::Utils.parse_directory
9
-
10
- abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless (id || options[:service])
9
+ abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless id
11
10
 
12
11
  FastlyCTL::Fetcher.api_request(:post, "/service/#{id}/purge_all")
13
12
 
@@ -18,7 +18,7 @@ module FastlyCTL
18
18
  version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
19
19
  version ||= options[:version].to_i
20
20
 
21
- encoded_name = URI.escape(name) if name
21
+ encoded_name = FastlyCTL::Utils.percent_encode(name) if name
22
22
 
23
23
  filename = options.key?(:filename) ? options[:filename] : "#{name}.snippet"
24
24
 
@@ -0,0 +1,70 @@
1
+ require "fastlyctl/commands/tls/managed"
2
+
3
+ module FastlyCTL
4
+ class TLSSubCmd < SubCommandBase
5
+ SubcommandPrefix = "tls"
6
+
7
+ desc "managed SUBCOMMAND ...ARGS", "Interface with Fastly Managed TLS Subscriptions (lets-encrypt)"
8
+ subcommand "managed", TLSManagedSubCmd
9
+ end
10
+
11
+ class CLI < Thor
12
+ desc "tls SUBCOMMAND ...ARGS", "Interface with Fastly TLS"
13
+ subcommand "tls", TLSSubCmd
14
+ end
15
+
16
+ module TLSUtils
17
+ def self.get_tls_configs
18
+ data = FastlyCTL::Fetcher.api_request(:get,"/tls/configurations",{use_vnd:true})["data"]
19
+ if data.length == 0
20
+ thor = Thor::Shell::Basic.new
21
+ thor.say "No TLS Configurations found. You may need to upgrade to a paid account if you are using a free account."
22
+ thor.say "If you need assistance, please contact support@fastly.com."
23
+ if (thor.yes?("Would you like to open the TLS configuration page in the Fastly app?"))
24
+ FastlyCTL::Utils.open_app_path("/network/domains")
25
+ end
26
+ abort
27
+ end
28
+
29
+ return data
30
+ end
31
+
32
+ def self.select_tls_config(configs)
33
+ thor = Thor::Shell::Basic.new
34
+ if configs.length == 1
35
+ thor.say "Using TLS Configuration #{configs[0]["id"]} - #{configs[0]["name"]}"
36
+ return configs[0]
37
+ end
38
+
39
+ loop do
40
+ i = 1
41
+ configs.each do |c|
42
+ bulk = c["attributes"]["bulk"] ? " [Platform TLS]" : ""
43
+ thor.say("[#{i}]#{bulk} #{c["id"]} - #{c["name"]}\n")
44
+ i += 1
45
+ end
46
+
47
+ selected = thor.ask("Which TLS Configuration would you like to use? Please type the number next to the configuration(s) above.").to_i
48
+ if selected > 0 && selected <= (configs.length+1)
49
+ selected -= 1
50
+ thor.say "Using TLS Configuration #{configs[selected]["id"]} - #{configs[selected]["name"]}"
51
+ return configs[selected]
52
+ end
53
+
54
+ thor.say "#{selcted} is in invalid selection. Please try again."
55
+ end
56
+ end
57
+
58
+ def self.print_challenges(tls_authorization)
59
+ thor = Thor::Shell::Basic.new
60
+ thor.say "\nIn order to verify your ownership of the domain, the Certificate Authority provided the following challenges:"
61
+ tls_authorization["attributes"]["challenges"].each do |challenge|
62
+ thor.say("\n#{challenge["type"]}: Create #{challenge["record_type"]} record for #{challenge["record_name"]} with value(s) of:")
63
+ challenge["values"].each do |val|
64
+ thor.say(" #{val}")
65
+ end
66
+ end
67
+ thor.say("\nNote: If you don't want to move all traffic to Fastly right now, use the managed-dns option. The other options result in traffic for that hostname being directed to Fastly.")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,119 @@
1
+ module FastlyCTL
2
+ class TLSManagedSubCmd < SubCommandBase
3
+ SubcommandPrefix = "tls managed"
4
+ DomainRegex = /(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/
5
+
6
+ desc "create <domain>", "Create a Fastly Managed TLS Subscription for [domain]. A Certificate will be requested from lets-encrypt once you satisfy one of the challenges. You can learn more about the challenge types here: https://letsencrypt.org/docs/challenge-types/ and Fastly's API documentation here: https://docs.fastly.com/api/tls-subscriptions."
7
+ def create(domain)
8
+ abort "Must specify valid domain name" unless domain =~ DomainRegex
9
+
10
+ tls_configs = FastlyCTL::TLSUtils.get_tls_configs
11
+ tls_config = FastlyCTL::TLSUtils.select_tls_config(tls_configs)
12
+
13
+ payload = {
14
+ data: {
15
+ type: "tls_subscription",
16
+ attributes: {
17
+ certificate_authority: "lets-encrypt"
18
+ },
19
+ relationships: {
20
+ tls_domains: {
21
+ data: [
22
+ {
23
+ type: "tls_domain",
24
+ id: domain
25
+ }
26
+ ]
27
+ },
28
+ tls_configuration: {
29
+ data: {
30
+ type: "tls_configuration",
31
+ id: tls_config["id"]
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ subscription = FastlyCTL::Fetcher.api_request(:post,"/tls/subscriptions", {
39
+ body: payload.to_json,
40
+ use_vnd: true
41
+ })
42
+
43
+ tls_authorization = FastlyCTL::Utils.filter_vnd(subscription["included"],"tls_authorization")
44
+ abort "Unable to fetch TLS Authorization for the domain." unless tls_authorization.length > 0
45
+ FastlyCTL::TLSUtils.print_challenges(tls_authorization[0])
46
+ end
47
+
48
+ desc "status", "Print status of Fastly Managed TLS Subscriptions"
49
+ def status
50
+ subscriptions = FastlyCTL::Fetcher.api_request(:get,"/tls/subscriptions", {
51
+ use_vnd: true
52
+ })
53
+
54
+ if subscriptions["data"].length == 0
55
+ say("No Fastly Managed TLS Subscriptions found.")
56
+ abort
57
+ end
58
+
59
+ subscriptions["data"].each do |subscription|
60
+ output = subscription["relationships"]["tls_domains"]["data"][0]["id"]
61
+ output += " - " + subscription["attributes"]["certificate_authority"]
62
+ output += " - " + subscription["attributes"]["state"]
63
+ say(output)
64
+ end
65
+ end
66
+
67
+ desc "challenges", "Print challenges available for a domain's verification."
68
+ def challenges(domain)
69
+ abort "Must specify valid domain name" unless domain =~ DomainRegex
70
+
71
+ domains = FastlyCTL::Fetcher.api_request(:get,"/tls/domains?include=tls_subscriptions.tls_authorizations", {
72
+ use_vnd: true
73
+ })
74
+
75
+ tls_authorizations = FastlyCTL::Utils.filter_vnd(domains["included"],"tls_authorization")
76
+
77
+ tls_authorizations.each do |tls_authorization|
78
+ tls_authorization["attributes"]["challenges"].each do |challenge|
79
+ if challenge["record_name"] == domain
80
+ FastlyCTL::TLSUtils.print_challenges(tls_authorization)
81
+ abort
82
+ end
83
+ end
84
+ end
85
+
86
+ say("#{domain} not found in domain list.")
87
+ end
88
+
89
+ desc "delete", "Delete a Fastly Managed TLS Subscription"
90
+ def delete(domain)
91
+ abort "Must specify valid domain name" unless domain =~ DomainRegex
92
+
93
+ activation = FastlyCTL::Fetcher.api_request(:get,"/tls/activations?filter[tls_domain.id]=#{domain}", {use_vnd: true})
94
+
95
+ if activation["data"].length >= 1
96
+ say("TLS is currently active for #{domain}. If you proceed, Fastly will no longer be able to serve TLS requests to clients for #{domain}.")
97
+ answer = ask("Please type the name of the domain to confirm deactivation and deletion of the Fastly Managed TLS subscription: ")
98
+ abort "Supplied domain does not match the domain requested for deletion--aborting." unless answer == domain
99
+
100
+ FastlyCTL::Fetcher.api_request(:delete,"/tls/activations/#{activation["data"][0]["id"]}",{use_vnd:true})
101
+ end
102
+
103
+ subscriptions = FastlyCTL::Fetcher.api_request(:get,"/tls/subscriptions", {
104
+ use_vnd: true
105
+ })
106
+
107
+ subscriptions["data"].each do |subscription|
108
+ next unless subscription["relationships"]["tls_domains"]["data"][0]["id"] == domain
109
+
110
+ FastlyCTL::Fetcher.api_request(:delete,"/tls/subscriptions/#{subscription["id"]}",{use_vnd:true})
111
+
112
+ say("TLS Subscription for #{domain} has been deleted.")
113
+ abort
114
+ end
115
+
116
+ say("No TLS Subscription found for #{domain}...")
117
+ end
118
+ end
119
+ end
@@ -20,25 +20,27 @@ module FastlyCTL
20
20
  scope = options[:scope]
21
21
  scope ||= "global"
22
22
 
23
- say("You must login again to create tokens.")
23
+ say("You must authenticate again to create tokens.")
24
24
 
25
- login_results = FastlyCTL::Fetcher.login
26
-
27
- name = ask("What would you like to name your token?")
25
+ username = ask("Username: ")
26
+ password = ask("Password: ", :echo => false)
27
+ say("")
28
28
 
29
+ name = ask("What would you like to name your token? (enter here):")
29
30
  o = {
30
- user: login_results[:user],
31
- pass: login_results[:pass],
32
- code: login_results[:code],
31
+ username: username,
32
+ password: password,
33
33
  scope: scope,
34
- name: name
35
- }
36
-
37
- o[:services] = options[:services].split(",") if options[:services]
34
+ name: name || "fastlyctl_token"
35
+ }.compare_by_identity
38
36
 
37
+ options[:services].split(",").each do |v|
38
+ o["services[]"] = v
39
+ end if options[:services]
39
40
  o[:customer] = options[:customer] if options[:customer]
40
41
 
41
42
  resp = FastlyCTL::Fetcher.create_token(o)
43
+ say("token: #{resp["access_token"]}")
42
44
 
43
45
  when "delete"
44
46
  id = ask("What is the ID of the token you'd like to delete?")
@@ -13,7 +13,7 @@ module FastlyCTL
13
13
  pop = pop.upcase if pop
14
14
 
15
15
  while true
16
- data = FastlyCTL::Fetcher.api_request(:get,"/rt/v1/channel/#{service}/ts/#{ts ? ts : 'h/limit/120'}", :endpoint => :app)
16
+ data = FastlyCTL::Fetcher.api_request(:get,"/rt/v1/channel/#{service}/ts/#{ts ? ts : 'h/limit/120'}", :endpoint => :rt)
17
17
 
18
18
  unless data["Data"].length > 0
19
19
  say("No data to display!")
@@ -33,7 +33,8 @@ module FastlyCTL
33
33
  # gbps
34
34
  uncacheable = agg["pass"] + agg["synth"] + agg["errors"]
35
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
36
+ shield = agg["shield"] || 0
37
+ hit_rate = (1.0 - ((agg["miss"] - shield).to_f / ((agg["requests"] - uncacheable).to_f))) * 100.0
37
38
  passes = agg["pass"]
38
39
  miss_time = agg["miss"] > 0 ? ((agg["miss_time"] / agg["miss"]) * 1000).round(0) : 0
39
40
  synth = agg["synth"]
@@ -5,28 +5,32 @@ module FastlyCTL
5
5
  options[:params] ||= {}
6
6
  options[:headers] ||= {}
7
7
  options[:body] ||= nil
8
- options[:force_session] ||= false
8
+ options[:disable_token] ||= false
9
9
  options[:expected_responses] ||= [200]
10
+ options[:use_vnd] ||= false
10
11
 
11
12
  headers = {"Accept" => "application/json", "Connection" => "close", "User-Agent" => "FastlyCTL: https://github.com/fastly/fastlyctl"}
12
13
 
13
14
  if options[:endpoint] == :app
14
15
  headers["Referer"] = FastlyCTL::FASTLY_APP
15
- headers["X-CSRF-Token"] = FastlyCTL::Cookies["fastly.csrf"] if FastlyCTL::Cookies["fastly.csrf"]
16
16
  headers["Fastly-API-Request"] = "true"
17
17
  end
18
18
 
19
- if FastlyCTL::Token && !options[:force_session]
19
+ if FastlyCTL::Token && !options[:disable_token]
20
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
21
  end
27
22
 
28
23
  headers["Content-Type"] = "application/x-www-form-urlencoded" if (method == :post || method == :put)
29
24
 
25
+ if options[:use_vnd]
26
+ headers["Accept"] = "application/vnd.api+json"
27
+
28
+ if (method == :post || method == :put)
29
+ headers["Content-Type"] = "application/vnd.api+json"
30
+ end
31
+ options[:expected_responses].push(*[201,202,203,204])
32
+ end
33
+
30
34
  headers.merge!(options[:headers]) if options[:headers].count > 0
31
35
 
32
36
  # dont allow header splitting on anything
@@ -34,7 +38,7 @@ module FastlyCTL
34
38
  headers[k] = v.gsub(/\r|\n/,'')
35
39
  end
36
40
 
37
- url = "#{options[:endpoint] == :api ? FastlyCTL::FASTLY_API : FastlyCTL::FASTLY_APP}#{path}"
41
+ url = "#{options[:endpoint] == :api ? FastlyCTL::FASTLY_API : FastlyCTL::FASTLY_RT_API}#{path}"
38
42
 
39
43
  response = Typhoeus::Request.new(
40
44
  url,
@@ -44,35 +48,43 @@ module FastlyCTL
44
48
  body: options[:body]
45
49
  ).run
46
50
 
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
51
+ if !options[:expected_responses].include?(response.response_code)
56
52
  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}."
53
+ when 400
54
+ error = "400: Bad API request--something was wrong with the request made by FastlyCTL."
55
+ when 403
56
+ error = "403: Access Denied by API. Run login command to authenticate."
57
+ when 404
58
+ error = "404: Service does not exist or bad path requested."
59
+ when 503
60
+ error = "503: Error from Fastly API--see details below."
61
+ when 0
62
+ error = "0: Network connection error occurred."
63
+ else
64
+ error = "API responded with status #{response.response_code}."
67
65
  end
68
66
 
69
67
  error += " Method: #{method.to_s.upcase}, Path: #{path}\n"
70
- error += "Message from API: #{response.response_body}"
68
+
69
+ if (options[:use_vnd])
70
+ begin
71
+ error_resp = JSON.parse(response.response_body)
72
+ rescue JSON::ParserError
73
+ error_resp = {"errors" => [{"title" => "Error parsing response JSON","details" => "No further information available. Please file a github issue at https://github.com/fastly/fastlyctl"}]}
74
+ end
75
+
76
+ error_resp["errors"].each do |e|
77
+ next unless e.key?("title") && e.key?("detail")
78
+ error += e["title"] + " --- " + e["detail"] + "\n"
79
+ end
80
+ else
81
+ error += "Message from API: #{response.response_body}"
82
+ end
71
83
 
72
84
  abort error
73
85
  end
74
86
 
75
- return response.response_body if (response.headers["Content-Type"] != "application/json")
87
+ return response.response_body unless (response.headers["Content-Type"] =~ /json$/)
76
88
 
77
89
  if response.response_body.length > 1
78
90
  begin
@@ -161,7 +173,7 @@ module FastlyCTL
161
173
  end
162
174
 
163
175
  def self.upload_snippet(service,version,content,name)
164
- return FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/snippet/#{name}", {:endpoint => :api, body: {
176
+ return FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/snippet/#{FastlyCTL::Utils.percent_encode(name)}", {:endpoint => :api, body: {
165
177
  content: content
166
178
  }
167
179
  })
@@ -178,7 +190,7 @@ module FastlyCTL
178
190
  end
179
191
  end
180
192
 
181
- response = FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/vcl/#{name}", {:endpoint => :api, body: params, expected_responses: [200,404]})
193
+ response = FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/vcl/#{FastlyCTL::Utils.percent_encode(name)}", {:endpoint => :api, body: params, expected_responses: [200,404]})
182
194
 
183
195
  # The VCL got deleted so recreate it.
184
196
  if response["msg"] == "Record not found"
@@ -186,61 +198,33 @@ module FastlyCTL
186
198
  end
187
199
  end
188
200
 
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
201
  def self.create_token(options)
214
202
  thor = Thor::Shell::Basic.new
215
203
 
216
204
  headers = {}
217
- headers["Fastly-OTP"] = options[:code] if options[:code]
218
-
219
- FastlyCTL::Fetcher.api_request(:post, "/sudo", {
220
- force_session: true,
205
+ resp = FastlyCTL::Fetcher.api_request(:post, "/tokens", {
206
+ disable_token: true,
221
207
  endpoint: :api,
222
- params: {
223
- user: options[:user],
224
- password: options[:pass]
225
- },
226
- headers: headers
208
+ body: options,
209
+ headers: headers,
210
+ expected_responses: [200,400]
227
211
  })
228
212
 
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]
213
+ if resp.has_key?("msg") && resp["msg"] == "2fa.verify"
214
+ thor.say("\nTwo factor auth enabled on account, second factor needed.")
215
+ code = thor.ask('Please enter verification code:', echo: false)
237
216
 
238
- resp = FastlyCTL::Fetcher.api_request(:post, "/tokens", {
239
- force_session: true,
240
- endpoint: :api,
241
- params: params,
242
- headers: headers
243
- })
217
+ headers = {}
218
+ headers["Fastly-OTP"] = code
219
+ resp = FastlyCTL::Fetcher.api_request(:post, "/tokens", {
220
+ disable_token: true,
221
+ endpoint: :api,
222
+ body: options,
223
+ headers: headers
224
+ })
225
+ elsif resp.has_key?("msg")
226
+ abort "ERROR: #{resp}"
227
+ end
244
228
 
245
229
  thor.say("\n#{resp["id"]} created.")
246
230
 
@@ -0,0 +1,5 @@
1
+ class SubCommandBase < Thor
2
+ def self.banner(command, namespace = nil, subcommand = false)
3
+ basename + " " + self::SubcommandPrefix + " " + command.usage
4
+ end
5
+ end
@@ -4,6 +4,10 @@ module FastlyCTL
4
4
  Launchy.open(FastlyCTL::FASTLY_APP + FastlyCTL::TANGO_PATH + id)
5
5
  end
6
6
 
7
+ def self.open_app_path(path)
8
+ Launchy.open(FastlyCTL::FASTLY_APP + path)
9
+ end
10
+
7
11
  def self.parse_directory(path=false)
8
12
  directory = Dir.pwd unless path
9
13
  directory = path if path
@@ -68,5 +72,21 @@ module FastlyCTL
68
72
 
69
73
  return diff
70
74
  end
75
+
76
+ def self.percent_encode(string)
77
+ # CGI.escape replace whitespace to "+" which is "%20" in a percent-encoding manner
78
+ CGI.escape(string).gsub('+', '%20')
79
+ end
80
+
81
+ def self.filter_vnd(haystack,needle)
82
+ results = []
83
+ haystack.each do |i|
84
+ next unless i["type"] == needle
85
+
86
+ results.push(i)
87
+ end
88
+
89
+ return results
90
+ end
71
91
  end
72
92
  end
@@ -1,3 +1,3 @@
1
1
  module FastlyCTL
2
- VERSION = "1.0.15"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlyctl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.15
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Basile
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-06 00:00:00.000000000 Z
11
+ date: 2021-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -72,7 +72,29 @@ dependencies:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: 2.4.3
75
- description: This gem provides a CLI for managing Fastly configurations
75
+ - !ruby/object:Gem::Dependency
76
+ name: openssl
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.1.2
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: 2.1.2
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 2.1.2
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: 2.1.2
95
+ description: 'DEPRECATED: FastlyCTL is no longer maintained or supported. Many features
96
+ are already available in the official Fastly CLI (https://github.com/fastly/cli),
97
+ which is actively maintained and developed. Please consider migrating if possible.'
76
98
  email:
77
99
  - stephen@fastly.com
78
100
  executables:
@@ -80,6 +102,7 @@ executables:
80
102
  extensions: []
81
103
  extra_rdoc_files: []
82
104
  files:
105
+ - ".github/issue_template.md"
83
106
  - ".gitignore"
84
107
  - Gemfile
85
108
  - Gemfile.lock
@@ -111,10 +134,13 @@ files:
111
134
  - lib/fastlyctl/commands/purge_all.rb
112
135
  - lib/fastlyctl/commands/skeleton.rb
113
136
  - lib/fastlyctl/commands/snippet.rb
137
+ - lib/fastlyctl/commands/tls.rb
138
+ - lib/fastlyctl/commands/tls/managed.rb
114
139
  - lib/fastlyctl/commands/token.rb
115
140
  - lib/fastlyctl/commands/upload.rb
116
141
  - lib/fastlyctl/commands/watch.rb
117
142
  - lib/fastlyctl/fetcher.rb
143
+ - lib/fastlyctl/subcommand_patch.rb
118
144
  - lib/fastlyctl/utils.rb
119
145
  - lib/fastlyctl/version.rb
120
146
  homepage: http://www.github.com/fastly/fastlyctl
@@ -122,7 +148,7 @@ licenses:
122
148
  - MIT
123
149
  metadata:
124
150
  allowed_push_host: https://rubygems.org/
125
- post_install_message:
151
+ post_install_message:
126
152
  rdoc_options: []
127
153
  require_paths:
128
154
  - lib
@@ -137,8 +163,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
163
  - !ruby/object:Gem::Version
138
164
  version: '0'
139
165
  requirements: []
140
- rubygems_version: 3.0.1
141
- signing_key:
166
+ rubygems_version: 3.0.8
167
+ signing_key:
142
168
  specification_version: 4
143
169
  summary: CLI tool for interacting with the Fastly API
144
170
  test_files: []