redfish_tools 0.1.2 → 0.2.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
  SHA1:
3
- metadata.gz: d591794d6dd81a861a334116e4ddc69d0e583f5f
4
- data.tar.gz: b801a69d947f93e521bc9f910727ad03485e5e07
3
+ metadata.gz: 62d5e62a684fe41e5f66474396da984161869dc0
4
+ data.tar.gz: 401e723d7f305dd2576e45352e19fe9b5da901e7
5
5
  SHA512:
6
- metadata.gz: 4c862e78f28fe165dda817dd3ce34a9501ec4ffd0cd511821f8a63dab0658e0e34b60b6cfd69b1367ccf10e7a28a68ea2dbd63147a52bb26105fe8376bc047fd
7
- data.tar.gz: 8beb0d8f83e83dbf03f00dade9c78a16d778efd94415f1cafef8cf3dd71405c98ad558c9d93944b9eb7a89db76008c16d8ba568c0238547165c7a44552617d79
6
+ metadata.gz: 26f1eb827d58a02555259bb9b618ac0444c5483599ffd5b2477e57fe15f4e10559431b3d041a21b2f1684370df60daf0fd73e50135f5f8136528165079fa9663
7
+ data.tar.gz: 7ec1d90be137f6006b9cf18dd75e622e03d5efccda563ef682200d3954a5a60f43acb8a1ef70f5b65e508e2cd6015d9079d1e18a1aedd988d91e9dc1ee603994
data/.travis.yml CHANGED
@@ -1,5 +1,14 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ cache: bundler
3
4
  rvm:
4
- - 2.4.3
5
- before_install: gem install bundler -v 1.16.1
5
+ - 2.4.3
6
+ script: true
7
+ deploy:
8
+ provider: rubygems
9
+ api_key:
10
+ secure: r3WiTmAZTHbqi30saYw0Iv2YEUZNAv4dp2RPzDoc7K9l1yV+Pt+fol/NCOg8gVBXPn5WHHrXUpw4jz158CneOPlmp7opdzW8SZymUlphXBl7kcI9qEsDJnqZOAJkj1YVwMvjMwRqE1UjWs43Ok93wVQlt2M9FIztLBXHhKtm+M0T6GpeFisAntgeIYiRl0/XTYr36SG23dTcWogkKEHahX24/W1ap5Z2O+p0wZNyRbhFXMMkvMVqwS6EFn9CR1GuJ2f93WYzsIZWm4LhjDZisAxh4oHtcGfXyOSHTU4d4fOnoBd0H7Ejyf/4mr407gnfSqpuXJUIiTsNPDrlMTGT0l/sSJYPWY6P1nygOTlCd7oLG+E3nOUKD5L0KPdzLkAxqylrNbCFx9dPMfyuT6wUin4XW/NulwCAy7qLrMFn0MuugxZINMJE09dWCMd5B+guMPtmpj7eVerrDi26H7hEO7Mdvg1rmhEfUVLFbiiAOzi3SvHhFYZC7Rr1WFt+8zUxgAUxwuAfAyJX4Zolkj1xcQF1Qssj4g8wAtaQ1ZWhdWWoJjEziJcqZPOIckeB+uuvauXVSd1a8ASsHLBCrnZUYKp6ZOn7a3t5zaoUjUTl4BuPZFfZ0gAqP3YMEmGboeDPFRM4uNk7l5eA5qc0s0HT2wgjYXeL+M2ZmHk5ACInWAw=
11
+ gem: redfish_tools
12
+ on:
13
+ tags: true
14
+ repo: xlab-si/redfish_tools
data/README.md CHANGED
@@ -1,16 +1,21 @@
1
- # RedfishTools
1
+ # Redfish tools
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/redfish_tools`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ [![Build Status](https://travis-ci.com/xlab-si/redfish_tools.svg?branch=master)](https://travis-ci.com/xlab-si/redfish_tools)
4
+
5
+
6
+ This repository contains source code for redfish_tools gem that contains tools
7
+ for testing application that know how to work with Redfish API.
8
+
9
+ The only tool that is currently available is mock server, but in the near
10
+ future, we will also add a mock creator tool and interactive inspector for
11
+ Redfish services.
4
12
 
5
- TODO: Delete this and the text above, and describe your gem
6
13
 
7
14
  ## Installation
8
15
 
9
16
  Add this line to your application's Gemfile:
10
17
 
11
- ```ruby
12
- gem 'redfish_tools'
13
- ```
18
+ gem "redfish_tools"
14
19
 
15
20
  And then execute:
16
21
 
@@ -20,16 +25,38 @@ Or install it yourself as:
20
25
 
21
26
  $ gem install redfish_tools
22
27
 
28
+
23
29
  ## Usage
24
30
 
25
- TODO: Write usage instructions here
31
+ The simplest way to start using Redfish tools is to simply run the `redfish`
32
+ command and read the provided help. At the moment, the output should look like
33
+ this:
34
+
35
+ $ redfish
36
+ Commands:
37
+ redfish help [COMMAND] # Describe available commands
38
+ redfish serve [OPTIONS] PATH # serve mock from PATH
39
+
40
+ To start serving existing Redfish recording, we run the `serve` command:
41
+
42
+ $ redfish serve --ssl --user test --pass demo path/to/recording
43
+
44
+ To get the description of all available options, use `help` command or add
45
+ `-h` flag anywhere in the command.
46
+
26
47
 
27
48
  ## Development
28
49
 
29
- 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.
50
+ After checking out the repo, run `bin/setup` to install dependencies.
51
+ Unfortunately, this gem contains no tests at the moment, so if you feel like
52
+ contributing, this would be a great place to start.
53
+
54
+ To create new release, increment the version number, commit the change, tag
55
+ the commit and push tag to the GitHub. Travis CI will pick from there on and
56
+ create new release, publishing it on https://rubygems.org.
30
57
 
31
- 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).
32
58
 
33
59
  ## Contributing
34
60
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/redfish_tools.
61
+ Bug reports and pull requests are welcome on GitHub at
62
+ https://github.com/xlab-si/redfish_tools.
@@ -14,8 +14,12 @@ module RedfishTools
14
14
  def run
15
15
  datastore = RedfishTools::DataStore.new(@path)
16
16
  server = RedfishTools::Server.new(datastore,
17
+ @options[:user],
18
+ @options[:pass],
17
19
  Port: @options[:port],
18
- BindAddress: @options[:bind])
20
+ BindAddress: @options[:bind],
21
+ SSLEnable: @options[:ssl],
22
+ SSLCertName: [%w[CN localhost]])
19
23
  trap("INT") { server.shutdown }
20
24
  server.start
21
25
  end
@@ -11,6 +11,10 @@ module RedfishTools
11
11
  super(args)
12
12
  end
13
13
 
14
+ def self.exit_on_failure?
15
+ true
16
+ end
17
+
14
18
  desc "serve [OPTIONS] PATH", "serve mock from PATH"
15
19
  option :port,
16
20
  desc: "port that should be used to serve the mock",
@@ -19,18 +23,24 @@ module RedfishTools
19
23
  option :bind,
20
24
  desc: "address that server should bind to",
21
25
  default: "127.0.0.1"
26
+ option :ssl,
27
+ desc: "use SSL",
28
+ default: false,
29
+ type: :boolean
30
+ option :user,
31
+ desc: "username to use"
32
+ option :pass,
33
+ desc: "password to use"
22
34
  def serve(path)
35
+ user = options[:user]
36
+ pass = options[:pass]
37
+ raise Thor::Error, "Missing password" if user && pass.nil?
38
+ raise Thor::Error, "Missing username" if user.nil? && pass
39
+
23
40
  require "redfish_tools/cli/serve"
24
41
  Serve.new(path, options).run
25
- end
26
-
27
- desc "query [OPTIONS] URL QUERY", "query service at URL using QUERY"
28
- def query(url, *query
29
-
30
- desc "record [OPTIONS] URL PATH", "record service at URL"
31
- def record(url, path)
32
- require "redfish_tools/cli/record"
33
- Record.new(url, path, options).run
42
+ rescue StandardError => e
43
+ raise Thor::Error, e.to_s
34
44
  end
35
45
  end
36
46
  end
@@ -7,14 +7,16 @@ module RedfishTools
7
7
  Resource = Struct.new(:id, :body, :headers, :time)
8
8
 
9
9
  def initialize(base_path)
10
- # TODO(tadeboro): check for folder and determine mode of operation
11
10
  @base_path = File.expand_path(base_path)
12
11
  @overlay = {}
12
+
13
+ root_file = File.join(@base_path, "redfish", "v1", "index.json")
14
+ raise "Invalid recording folder" unless File.file?(root_file)
13
15
  end
14
16
 
15
17
  def get(id)
16
18
  id = id.chomp("/")
17
- @overlay[id] || load_resource(id)
19
+ @overlay[id] ||= load_resource(id)
18
20
  end
19
21
 
20
22
  def set(id, body, headers: nil, time: nil)
@@ -28,24 +30,21 @@ module RedfishTools
28
30
  end
29
31
 
30
32
  def load_body(id)
31
- load(File.join(@base_path, id, "index.json"))
33
+ load_json(File.join(@base_path, id, "index.json"))
32
34
  end
33
35
 
34
36
  def load_headers(id)
35
- load_json(File.join(@base_path, id, "headers.json"))["GET"]
37
+ headers = load_json(File.join(@base_path, id, "headers.json"))
38
+ headers && headers["GET"]
36
39
  end
37
40
 
38
41
  def load_time(id)
39
- load_json(File.join(@base_path, id, "time.json"))["GET_Time"].to_f
40
- end
41
-
42
- def load(path)
43
- File.readable?(path) ? File.read(path) : nil
42
+ times = load_json(File.join(@base_path, id, "time.json"))
43
+ times && times["GET_Time"]&.to_f
44
44
  end
45
45
 
46
46
  def load_json(path)
47
- content = load(path)
48
- content ? JSON.parse(content) : {}
47
+ File.readable?(path) ? JSON.parse(File.read(path)) : nil
49
48
  end
50
49
  end
51
50
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
3
4
  require "openssl"
4
5
  require "webrick"
5
6
  require "webrick/https"
@@ -8,12 +9,23 @@ require "redfish_tools/servlet"
8
9
 
9
10
  module RedfishTools
10
11
  class Server < WEBrick::HTTPServer
11
- def initialize(datastore, config = {})
12
+ def initialize(datastore, username, password, config = {})
12
13
  super(config)
13
14
 
14
- root = JSON.parse(datastore.get("/redfish/v1").body)
15
- login_path = root.dig("Links", "Sessions", "@odata.id")
16
- mount("/", Servlet, datastore, login_path)
15
+ @datastore = datastore
16
+ root = datastore.get("/redfish/v1").body
17
+ @login_path = root.dig("Links", "Sessions", "@odata.id")&.chomp("/")
18
+ @username = username
19
+ @password = password
20
+
21
+ mount("/", Servlet)
22
+ end
23
+
24
+ attr_reader :datastore, :login_path, :username, :password
25
+
26
+ def basic_auth_header
27
+ @basic_auth_header ||= "Basic " +
28
+ Base64.strict_encode64("#{username}:#{password}")
17
29
  end
18
30
  end
19
31
  end
@@ -1,38 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+ require "json"
5
+ require "securerandom"
3
6
  require "set"
4
7
  require "webrick"
5
8
 
6
9
  module RedfishTools
7
10
  class Servlet < WEBrick::HTTPServlet::AbstractServlet
11
+ extend Forwardable
12
+
13
+ def_delegators :@server,
14
+ :datastore, :login_path, :username, :password,
15
+ :basic_auth_header
16
+
8
17
  BAD_HEADERS = Set.new(["connection", "content-length", "keep-alive"])
9
18
  DEFAULT_HEADERS = {
10
19
  "content-type" => "application/json"
11
20
  }.freeze
12
21
 
13
- def initialize(server, datastore, login_path)
14
- super(server)
15
- @datastore = datastore
16
- @login_path = login_path.chomp("/")
22
+ def service(request, response)
23
+ return response.status = 401 unless authorized?(request)
24
+ return response.status = 404 unless datastore.get(request.path).body
25
+
26
+ super
17
27
  end
18
28
 
19
29
  def do_GET(request, response)
20
- return response.status = 401 unless authorized?(request)
21
-
22
- resource = @datastore.get(request.path)
23
- response.body = resource.body
24
- set_headers(response, resource.headers)
25
- response.status = response.body ? 200 : 404
30
+ item = datastore.get(request.path)
31
+ response.status = 200
32
+ set_headers(response, item.headers)
33
+ response.body = item.body.to_json
26
34
  end
27
35
 
28
36
  def do_POST(request, response)
29
- return response.status = 401 unless authorized?(request)
37
+ item = datastore.get(request.path)
38
+ return response.status = 405 unless item.body["Members"]
30
39
 
31
- if login_path?(request.path)
32
- login(request, response)
33
- else
34
- response.status = 501
35
- end
40
+ data = JSON.parse(request.body)
41
+ item_n = login_path?(request) ? login(item, data) : new_item(item, data)
42
+ return response.status = 400 unless item_n.body
43
+
44
+ response.status = 201
45
+ set_headers(response, item_n.headers)
46
+ response.body = item_n.body.to_json
47
+ rescue JSON::ParserError
48
+ response.status = 400
36
49
  end
37
50
 
38
51
  def do_PUT(_request, response)
@@ -49,24 +62,65 @@ module RedfishTools
49
62
 
50
63
  private
51
64
 
52
- def login_path?(path)
53
- @login_path == path.chomp("/")
65
+ def login_path?(request)
66
+ login_path == request.path.chomp("/")
54
67
  end
55
68
 
56
- def login(request, response)
57
- credentials = JSON.parse(request.body)
58
- response.body = {
59
- "Name" => "User Session",
60
- "Description" => "User Session",
61
- "UserName" => credentials["UserName"]
62
- }.to_json
63
- set_headers(response, DEFAULT_HEADERS.merge("X-Auth-Token" => "dummy"))
64
- response.status = 201
69
+ def login(item, data)
70
+ user = data["UserName"]
71
+ pass = data["Password"]
72
+ return nil unless username == user && password == pass
73
+
74
+ res = new_item(item,
75
+ "@odata.type" => "#Session.v1_1_0.Session",
76
+ "UserName" => user,
77
+ "Password" => nil)
78
+ res.headers = DEFAULT_HEADERS.merge("X-Auth-Token" => res.body["Id"])
79
+ res
80
+ end
81
+
82
+ def new_item(item, data)
83
+ id = SecureRandom.uuid
84
+ oid = item.id.chomp("/") + "/" + id
85
+ item.body["Members@odata.count"] += 1
86
+ item.body["Members"].push("@odata.id" => oid)
87
+
88
+ base = { "@odata.id" => oid, "Id" => id, "Name" => id }
89
+ datastore.set(id, base.merge(data))
90
+ end
91
+
92
+ def authorized?(request)
93
+ username.nil? || # Server has no credentials set
94
+ always_allow?(request.path) || # Non-protected endpoints
95
+ authorized_basic?(request) ||
96
+ authorized_session?(request) ||
97
+ (request.request_method == "POST" && login_path?(request))
98
+ end
99
+
100
+ def always_allow?(path)
101
+ [
102
+ "/redfish",
103
+ "/redfish/v1",
104
+ "/redfish/v1/$metadata",
105
+ "/redfish/v1/odata"
106
+ ].include?(path.chomp("/"))
107
+ end
108
+
109
+ def authorized_basic?(request)
110
+ request["authorization"] == basic_auth_header
111
+ end
112
+
113
+ def authorized_session?(request)
114
+ return false if login_path.nil? || request["X-Auth-Token"].nil?
115
+ remove_stale_sessions
116
+ datastore.get(login_path).body["Members"].any? do |session|
117
+ session["@odata.id"].index(request["X-Auth-Token"])
118
+ end
65
119
  end
66
120
 
67
- def authorized?(_request)
68
- # TODO(tadeboro): Add checks as per Redfish standard
69
- true
121
+ def remove_stale_sessions
122
+ # TODO(tadeboro): implement session expiration
123
+ nil
70
124
  end
71
125
 
72
126
  def set_headers(response, headers)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RedfishTools
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redfish_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tadej Borovšak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-12 00:00:00.000000000 Z
11
+ date: 2018-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redfish_client