HewlettPackard/oneview-puppet

View on GitHub
lib/puppet/provider/common.rb

Summary

Maintainability
B
4 hrs
Test Coverage
################################################################################
# (C) Copyright 2016-2021 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

require 'json'
require_relative 'uri_parsing'

# ============== Common methods ==============

def pretty(arg)
  return puts arg if arg.instance_of?(String)
  puts JSON.pretty_generate(arg)
end

# Prepares environment variables needed to manage most resources.
# Transforms 'nil', 'false' and 'true' values passed in as strings into their respective types.
# Creates the @data global variable.
# Calls the 'data_parse' method specific to each resource if it exists.
# Retrieves resource uris using names passed inside uri fields.
def prepare_environment
  data = resource['data'] || {}
  data.each do |key, value|
    data[key] = nil if value == 'nil'
    data[key] = false if value == 'false'
    data[key] = true if value == 'true'
    data[key] = data[key].to_i if key == 'vlanId'
  end
  uri_validation(data)
  @data = data
  data_parse
end

# Updates resource if it exists and is different from the expected.
# Returns false if resource does not exist and true if it exists or if it was updated.
def resource_update
  @new_name = @data.delete('new_name')
  @item = @resource_type.new(@client, @data)
  return false unless @item.retrieve!
  parse_new_name
  if @item.like?(@data)
    Puppet.notice "#{@resource_type} #{@data['name']} is up to date."
  else
    Puppet.notice "#{@resource_type} #{@data['name']} differs from resource in appliance."
    Puppet.debug "Current attributes: #{JSON.pretty_generate(@item.data)}"
    Puppet.debug "Desired attributes: #{JSON.pretty_generate(@data)}"
    @item.update(@data)
    @property_hash[:data] = @item.data
  end
  true
end

# Validation for name change on resource through flag 'new_name'
def parse_new_name
  return unless @new_name
  raise 'new_name field contains an existing resource name.' if @resource_type.new(@client, name: @new_name).retrieve!
  @data['name'] = @new_name
end

def get_single_resource_instance
  # Expects to find exactly 1 resources with the data provided, otherwise fails
  # This should NOT be used for deletes/destroys as it can be used without a unique identifier in a context where only one resouce exists.
  found_resource = @resource_type.find_by(@client, @data)
  raise 'More than one resource matched the data specified. Specify a unique identifier on data.' if found_resource.size > 1
  raise 'No resources with the specified data specified were found. Specify a valid unique identifier on data.' if found_resource.empty?
  found_resource.first
end

def objectfromstring(str)
  # capitalizing the first letter + getting the remaining ones as they are
  # '.capitalize' alone will return something like Firstlettercapitalizedonly
  Object.const_get("OneviewSDK::#{str.to_s[0].upcase}#{str[1..str.size]}")
end

def find_resources
  # Searches ServerHardware with data matching the manifest data
  retrieved_resources = @resource_type.find_by(@client, @data)
  resource_name = @resource_type.to_s.split('::').last
  # If resources are found, iterate through them and notify. Else just notify.
  raise "\n\nNo #{resource_name} with the specified data were found on the Oneview Appliance\n" if retrieved_resources.empty?
  retrieved_resources.each do |retrieved_resource|
    Puppet.notice "\n\n Found matching #{resource_name} #{retrieved_resource['name']} "\
    "(URI: #{retrieved_resource['uri']}) on Oneview Appliance\n"
  end
  true
end

# Gets a resource by its unique identifier (generally name or uri)
def unique_id
  id = {}
  %w(uri name id providerDisplayName credentials providerUri poolName).each { |key| id[key] = @data[key] if @data[key] }
  raise 'A unique identifier for the resource must be declared in data for the current operation' if id.empty?
  id
end

# Returns an error in case the state requires @data not to be empty
# Takes as arguments the states that can be executed without data
def empty_data_check(states = [nil, :found])
  raise('This action requires the resource data to be declared in the manifest.') if @data.empty? && !states.include?(resource['ensure'])
  return true if states.include?(resource['ensure'])
end

# Returns the connection uri based on its name and functionType (Ethernet, FC, Network Set)
# FCoE to be added (no need so far)
def connections_parse
  @data.each_key do |key|
    next unless @data[key].is_a?(Hash)
    next unless @data[key].include?('manageConnections')
    @data[key].each do |conn, value|
      next unless conn.include?('connections')
      network_parse(value)
    end
  end
end

# This method will bypass exists method for bulk_delete method
def exists_bulk_method(states = [nil, :found])
  prepare_environment
  @item = @resource_type.new(@client, @data)
  return true if empty_data_check(states)
  @property_hash[:ensure] == :present
end

def network_parse(connections)
  connections.each do |network|
    next if network['networkUri'].to_s[0..6].include?('/rest/')
    type = case network['functionType']
           when 'Ethernet' then 'EthernetNetwork'
           when 'FibreChannel' then 'FCNetwork'
           when 'Set'
             network['functionType'] = 'Ethernet'
             'NetworkSet'
           end
    net = objectfromstring(type).find_by(@client, name: network['networkUri'])
    raise("The network #{network['networkUri']} does not exist in the Appliance.") unless net.first
    network['networkUri'] = net.first['uri']
  end
end

# Retrieve a resource by type and identifier (name or data)
# @param [String, Symbol] type of resource to be retrieved. e.g., :GoldenImage, :FCNetwork
# @param [String, Symbol, Hash] id Name of the resource or Hash of data to retrieve by.
#   Examples: 'EthNet1', { uri: '/rest/fake/123ABC' }
# @param [NilClass, String, Symbol] ret_attribute If specified, returns a specific attribute of the resource.
#   When nil, the complete resource will be returned.
# @param [Module] base_module Module to which the desired class belongs. Usually OneViewSDK or OneviewSDK::ImageStreamer.
# @return [OneviewSDK::Resource] if the `ret_attribute` is nil
# @return [String, Array, Hash] the value of the resource attribute defined by `ret_attribute`
# @raise [OneviewSDK::NotFound] ResourceNotFound if the resource cannot be found
# @raise [OneviewSDK::IncompleteResource] If you don't specify any unique identifiers in `id`
def load_resource(type, id, ret_attribute: nil, base_module: OneviewSDK)
  raise(ArgumentError, 'Must specify a resource type') unless type
  return unless id
  klass = base_module.resource_named(type, api_version, resource_variant)
  data = id.is_a?(Hash) ? id : { name: id }
  r = klass.new(@client, data)
  raise(OneviewSDK::NotFound, "#{type} with data '#{data}' was not found") unless r.retrieve!
  return r unless ret_attribute
  r[ret_attribute]
end

# Retrieve the template to be used for the resource and autofills blank fields with template's values
# @param [String] uri Uri of the resource
# @param [String] method Method specific to the resource which retrieves the require template
# @param [String, Symbol] type Type of resource to be retrieved. e.g., :ServerProfileTemplate, :ServerProfile
# @return [Hash] Updated resource data
# @raise [OneviewSDK::NotFound] ResourceNotFound if the resource cannot be found
def load_template(uri, method, type = :ServerProfileTemplate)
  return unless uri
  template = load_resource(type, uri: uri)
  template_data = template.send(method, @data['name']).data
  resource['data'] = template_data.merge(@data)
end