Skip to content

Commit

Permalink
Merge pull request #57 from nwops/template_merger
Browse files Browse the repository at this point in the history
Adds ability to supply template files to catalog request
  • Loading branch information
JJ Asghar authored Sep 19, 2017
2 parents 70d7d1c + 5150904 commit a05dc51
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 42 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,43 @@ vra.resources.all_resources
vra.requests.all_requests
```

### Download VRA catalog templates
It can be quite useful to download the catalog templates from your VRA server for future playback or inspection. This
can now be easily done with some helpful class methods.

To get a json string representation of the catalog template you can use `Vra::CatalogItem.dump_template(client, catalog_id)`

To dump the catalog template to a file instead of a string `Vra::CatalogItem.write_template(client, catalog_id)`. This will create a file like `windows2012.json`.

If you just want to dump all the templates you can use `Vra::CatalogItem.dump_templates(client)`. This will create a directory named vra_templates
with all the entitled templates for the current user.

There are additional options you can provide to these methods in order to customize output file names and directories, so please see the source code in lib/vra/catalog_item.rb

### Supply custom VRA catalog template and create request
If you previously had some custom templates already in JSON format you can now use those to create requests instead of setting
a bunch of parameters. This can be especially useful when your request has complex parameters or you have a bunch of templates
that you want to create unique requests for.

To use you can do something like:

```ruby
payload_file = '/tmp/windows_2012.json' # must be in json format
cr = Vra::CatalogRequest.request_from_payload(client, payload_file)
cr.submit

```

If you already have a catalog request object you can still supply a custom payload by simply setting the template_payload property.
Note this custom payload will be merged with the properties originally set when creating the catalog request object.

```ruby
cr = Vra::CatalogRequest.new(id, opts)
cr.template_payload = File.read('/tmp/windows_2012.json')
cr.submit

```

### Pagination

vRA paginates API requests where lists of items are returned. By default, this gem will ask vRA to provide items in groups of 20. However, as reported in [Issue 10](https://github.com/chef-partners/vmware-vra-gem/issues/10), it appears vRA may have a pagination bug. You can change the default page size from 20 to a value of your choice by passing in a `page_size` option when setting up the client:
Expand Down
41 changes: 41 additions & 0 deletions lib/vra/catalog_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#

require "ffi_yajl"
require "vra/catalog"

module Vra
class CatalogItem
Expand Down Expand Up @@ -85,5 +86,45 @@ def subtenant_name
def blueprint_id
@catalog_item_data["providerBinding"]["bindingId"]
end

# @param [String] - the id of the catalog item
# @param [Vra::Client] - a vra client object
# @return [String] - returns a json string of the catalog template
def self.dump_template(client, id)
response = client.http_get("/catalog-service/api/consumer/entitledCatalogItems/#{id}/requests/template")
response.body
end

# @param client [Vra::Client] - a vra client object
# @param id [String] - the id of the catalog item
# @param filename [String] - the name of the file you want to output the template to
# if left blank, will default to the id of the item
# @note outputs the catalog template to a file in serialized format
def self.write_template(client, id, filename = nil)
filename ||= "#{id}.json"
begin
contents = dump_template(client, id)
data = JSON.parse(contents)
pretty_contents = JSON.pretty_generate(data)
File.write(filename, pretty_contents)
return filename
rescue Vra::Exception::HTTPError => e
raise e
end
end

# @param [Vra::Client] - a vra client object
# @param [String] - the directory path to write the files to
# @param [Boolean] - set to true if you wish the file name to be the id of the catalog item
# @return [Array[String]] - a array of all the files that were generated
def self.dump_templates(client, dir_name = "vra_templates", use_id = false)
FileUtils.mkdir(dir_name) unless File.exist?(dir_name)
client.catalog.entitled_items.map do |c|
id = use_id ? c.id : c.name.tr(" ", "_")
filename = File.join(dir_name, "#{id}.json").downcase
write_template(client, c.id, filename)
filename
end
end
end
end
43 changes: 36 additions & 7 deletions lib/vra/catalog_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
require "vra/catalog_item"

module Vra
class CatalogRequest
attr_reader :catalog_id, :catalog_item, :client, :custom_fields
attr_writer :subtenant_id
attr_accessor :cpus, :memory, :requested_for, :lease_days, :notes
attr_accessor :cpus, :memory, :requested_for, :lease_days, :notes, :template_payload

def initialize(client, catalog_id, opts = {})
@client = client
Expand All @@ -33,10 +34,29 @@ def initialize(client, catalog_id, opts = {})
@notes = opts[:notes]
@subtenant_id = opts[:subtenant_id]
@additional_params = opts[:additional_params] || Vra::RequestParameters.new

@catalog_item = Vra::CatalogItem.new(client, id: catalog_id)
end

# @param payload_file [String] - A json payload that represents the catalog template you want to merge with this request
# @param client [Vra::Client] - a vra client object
# @return [Vra::CatalogRequest] - a request with the given payload merged
def self.request_from_payload(client, payload_file)
hash_payload = JSON.parse(File.read(payload_file))
catalog_id = hash_payload["catalogItemId"]
blueprint_name = hash_payload["data"].select { |_k, v| v.is_a?(Hash) }.keys.first
blueprint_data = hash_payload["data"][blueprint_name]
opts = {}
opts[:cpus] = blueprint_data["data"]["cpu"]
opts[:memory] = blueprint_data["data"]["memory"]
opts[:requested_for] = hash_payload["requestedFor"]
opts[:lease_days] = blueprint_data.fetch("leaseDays", nil) || hash_payload["data"].fetch("_lease_days", 1)
opts[:description] = hash_payload["description"]
opts[:subtenant_id] = hash_payload["businessGroupId"]
cr = Vra::CatalogRequest.new(client, catalog_id, opts)
cr.template_payload = File.read(payload_file)
cr
end

def set_parameter(key, type, value)
@additional_params.set(key, type, value)
end
Expand Down Expand Up @@ -66,31 +86,40 @@ def validate_params!
raise ArgumentError, "Unable to submit request, required param(s) missing => #{missing_params.join(', ')}" unless missing_params.empty?
end

# @return [String] - the current catalog template payload merged with the settings applied from this request
# @param [String] - A json payload that represents the catalog template you want to merge with this request
def merge_payload(payload)
hash_payload = JSON.parse(payload)
blueprint_name = hash_payload["data"].select { |_k, v| v.is_a?(Hash) }.keys.first

hash_payload["data"][blueprint_name]["data"]["cpu"] = @cpus
hash_payload["data"][blueprint_name]["data"]["memory"] = @memory
hash_payload["requestedFor"] = @requested_for
hash_payload["data"]["_leaseDays"] = @lease_days
hash_payload["description"] = @notes

JSON.pretty_generate(deep_merge(hash_payload, parameters))
end

# @return [String] - the current catalog template payload merged with the settings applied from this request
def merged_payload
merge_payload(template_payload)
end

# @return [String] - the current catalog template payload from VRA or custom payload set in JSON format
def template_payload
@template_payload ||= Vra::CatalogItem.dump_template(client, @catalog_id)
end

# @return [Vra::Request] - submits and returns the request, validating before hand
def submit
validate_params!

begin
response = client.http_get("/catalog-service/api/consumer/entitledCatalogItems/#{@catalog_id}/requests/template")
post_response = client.http_post("/catalog-service/api/consumer/entitledCatalogItems/#{@catalog_id}/requests", merge_payload(response.body))
post_response = client.http_post("/catalog-service/api/consumer/entitledCatalogItems/#{@catalog_id}/requests", merged_payload)
rescue Vra::Exception::HTTPError => e
raise Vra::Exception::RequestError, "Unable to submit request: #{e.errors.join(', ')}"
rescue
raise
end

request_id = JSON.parse(post_response.body)["id"]
Vra::Request.new(client, request_id)
end
Expand Down
144 changes: 144 additions & 0 deletions spec/catalog_item_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@
}
end

let(:other_catalog_item_payload) do
{
"@type" => "CatalogItem",
"id" => "3232323e-5443-4082-afd5-ab5a32939bbc",
"version" => 2,
"name" => "CentOS 6.6",
"description" => "Blueprint for deploying a CentOS Linux development server",
"status" => "PUBLISHED",
"statusName" => "Published",
"organization" => {
"tenantRef" => "vsphere.local",
"tenantLabel" => "vsphere.local",
"subtenantRef" => "962ab3f9-858c-4483-a49f-fa97392c314b",
"subtenantLabel" => "catalog_subtenant",
},
"providerBinding" => {
"bindingId" => "33af5413-4f20-4b3b-8268-32edad434dfb",
"providerRef" => {
"id" => "c3b2bc30-47b0-454f-b57d-df02a7356fe6",
"label" => "iaas-service",
},
},
}
end

describe "#initialize" do
it "raises an error if no ID or catalog item data have been provided" do
expect { Vra::CatalogItem.new }.to raise_error(ArgumentError)
Expand Down Expand Up @@ -135,5 +160,124 @@
expect(catalog_item.organization["tenantRef"]).to eq(nil)
end
end

describe "class methods" do
let(:response) { double("response", code: 200, body: catalog_item_payload.to_json) }

it "#dump_template" do
expect(client).to receive(:http_get).with("/catalog-service/api/consumer/entitledCatalogItems/#{catalog_id}/requests/template")
.and_return(response)
described_class.dump_template(client, catalog_id )
end

it "#write_template" do
allow(client).to receive(:http_get).with("/catalog-service/api/consumer/entitledCatalogItems/#{catalog_id}/requests/template")
.and_return(response)
expect(File).to receive(:write).with("9e98042e-5443-4082-afd5-ab5a32939bbc.json", JSON.pretty_generate(catalog_item_payload))
expect(described_class.write_template(client, catalog_id)).to eq("9e98042e-5443-4082-afd5-ab5a32939bbc.json")
end

it "#write_template with custom filename" do
allow(client).to receive(:http_get).with("/catalog-service/api/consumer/entitledCatalogItems/#{catalog_id}/requests/template")
.and_return(response)
expect(File).to receive(:write).with("somefile.json", JSON.pretty_generate(catalog_item_payload))
expect(described_class.write_template(client, catalog_id, "somefile.json")).to eq("somefile.json")
end

context "entitled items" do
let(:response2) { double("response", code: 200, body: other_catalog_item_payload.to_json) }

let(:entitled_catalog_item) do
{
"@type" => "ConsumerEntitledCatalogItem",
"catalogItem" => {
"id" => "d29efd6b-3cd6-4f8d-b1d8-da4ddd4e52b1",
"version" => 2,
"name" => "WindowsServer2012",
"description" => "Windows Server 2012 with the latest updates and patches.",
"status" => "PUBLISHED",
"statusName" => "Published",
"organization" => {
"tenantRef" => "vsphere.local",
"tenantLabel" => "vsphere.local",
"subtenantRef" => nil,
"subtenantLabel" => nil,
},
"providerBinding" => {
"bindingId" => "59fd02a1-acca-4918-9d3d-2298d310caef",
"providerRef" => {
"id" => "c3b2bc30-47b0-454f-b57d-df02a7356fe6",
"label" => "iaas-service",
},
},
},
}
end

let(:entitled_catalog_item2) do
{
"@type" => "ConsumerEntitledCatalogItem",
"catalogItem" => {
"id" => "3232323e-5443-4082-afd5-ab5a32939bbc",
"version" => 2,
"name" => "WindowsServer2016",
"description" => "Windows Server 2012 with the latest updates and patches.",
"status" => "PUBLISHED",
"statusName" => "Published",
"organization" => {
"tenantRef" => "vsphere.local",
"tenantLabel" => "vsphere.local",
"subtenantRef" => nil,
"subtenantLabel" => nil,
},
"providerBinding" => {
"bindingId" => "59fd02a1-acca-4918-9d3d-2298d310caef",
"providerRef" => {
"id" => "c3b2bc30-47b0-454f-b57d-df02a7356fe6",
"label" => "iaas-service",
},
},
},
}
end

before(:each) do
allow(client).to receive(:http_get_paginated_array!).with("/catalog-service/api/consumer/entitledCatalogItems")
.and_return([ entitled_catalog_item, entitled_catalog_item2 ])
allow(client).to receive(:http_get)
.with("/catalog-service/api/consumer/entitledCatalogItems/d29efd6b-3cd6-4f8d-b1d8-da4ddd4e52b1/requests/template")
.and_return(response)
allow(client).to receive(:http_get)
.with("/catalog-service/api/consumer/entitledCatalogItems/3232323e-5443-4082-afd5-ab5a32939bbc/requests/template")
.and_return(response)
allow(File).to receive(:write).with("vra_templates/d29efd6b-3cd6-4f8d-b1d8-da4ddd4e52b1.json", JSON.pretty_generate(catalog_item_payload))
allow(File).to receive(:write).with("vra_templates/3232323e-5443-4082-afd5-ab5a32939bbc.json", JSON.pretty_generate(catalog_item_payload))
allow(File).to receive(:write).with("vra_templates/windowsserver2012.json", JSON.pretty_generate(catalog_item_payload))
allow(File).to receive(:write).with("vra_templates/windowsserver2016.json", JSON.pretty_generate(catalog_item_payload))
allow(File).to receive(:write).with("custom_dir/windowsserver2012.json", JSON.pretty_generate(catalog_item_payload))
allow(File).to receive(:write).with("custom_dir/windowsserver2016.json", JSON.pretty_generate(catalog_item_payload))

end

it "#dump_templates" do
expect(described_class.dump_templates(client)).to eq(["vra_templates/windowsserver2012.json",
"vra_templates/windowsserver2016.json"])
end

it "#dump_templates with custom directory" do
expect(described_class.dump_templates(client, "custom_dir")).to eq(["custom_dir/windowsserver2012.json",
"custom_dir/windowsserver2016.json"])
end

it "#dump_templates with id" do
expect(described_class.dump_templates(client, "vra_templates", true))
.to eq(["vra_templates/d29efd6b-3cd6-4f8d-b1d8-da4ddd4e52b1.json",
"vra_templates/3232323e-5443-4082-afd5-ab5a32939bbc.json"])

end
end

end

end
end
Loading

0 comments on commit a05dc51

Please sign in to comment.