From edcd126dab0cd2fcca524d3838205f6c3c644257 Mon Sep 17 00:00:00 2001
From: Emilien Macchi <emilien@redhat.com>
Date: Fri, 27 Sep 2019 12:08:58 -0400
Subject: [PATCH] Fix properties in nova_aggregate provider for osc >= 4.0.0

Similar to I6a68505d15473b140c85a199a09d2fee45864800

Openstackclient 4.0.0 changed the way some properties are displayed
on screen.

Old:
...,"Properties"
...,"foo='bar'"

New:
...,"Properties"
...,"{u'foo': u'bar'}"
or
...,"{'foo': 'bar'}"

This is breaking idempotency on the nova_aggregate provider, since it
does not detect them correctly. This patch aims at fixing this, by
trying to detect the new format, and using JSON parsing in that case.

Closes-Bug: #1845616
Depends-On: https://review.opendev.org/#/c/685537/
Change-Id: I7e8fef9fdb913e53fa459ce09577f574fd059a13
---
 .../provider/nova_aggregate/openstack.rb      | 20 +++++++++++++++-
 .../provider/nova_aggregate/openstack_spec.rb | 24 +++++++++++++++++++
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/lib/puppet/provider/nova_aggregate/openstack.rb b/lib/puppet/provider/nova_aggregate/openstack.rb
index 40831c820..23e8afa18 100644
--- a/lib/puppet/provider/nova_aggregate/openstack.rb
+++ b/lib/puppet/provider/nova_aggregate/openstack.rb
@@ -15,7 +15,7 @@ Puppet::Type.type(:nova_aggregate).provide(
   def self.instances
     request('aggregate', 'list').collect do |el|
       attrs = request('aggregate', 'show', el[:name])
-      properties = Hash[attrs[:properties].scan(/(\S+)='([^']*)'/)] rescue nil
+      properties = parsestring(attrs[:properties]) rescue nil
       new(
           :ensure => :present,
           :name => attrs[:name],
@@ -118,4 +118,22 @@ Puppet::Type.type(:nova_aggregate).provide(
     end
   end
 
+  def self.string2hash(input)
+    return Hash[input.scan(/(\S+)='([^']*)'/)]
+  end
+
+  def self.pythondict2hash(input)
+    return JSON.parse(input.gsub(/u'(\w*)'/, '"\1"').gsub(/'/, '"'))
+  end
+
+  def self.parsestring(input)
+    if input[0] == '{'
+      # 4.0.0+ output, python dict
+      return self.pythondict2hash(input)
+    else
+      # Pre-4.0.0 output, key=value
+      return self.string2hash(input)
+    end
+  end
+
 end
diff --git a/spec/unit/provider/nova_aggregate/openstack_spec.rb b/spec/unit/provider/nova_aggregate/openstack_spec.rb
index 0d18ffea1..fc02197c7 100644
--- a/spec/unit/provider/nova_aggregate/openstack_spec.rb
+++ b/spec/unit/provider/nova_aggregate/openstack_spec.rb
@@ -96,6 +96,30 @@ hosts="[u\'example\']"
         end
       end
 
+      describe '#pythondict2hash' do
+        it 'should return a hash with key-value when provided with a unicode python dict' do
+          s = "{u'key': 'value', u'key2': 'value2'}"
+          expect(provider_class.pythondict2hash(s)).to eq({"key"=>"value", "key2"=>"value2"})
+        end
+
+        it 'should return a hash with key-value when provided with a python dict' do
+          s = "{'key': 'value', 'key2': 'value2'}"
+          expect(provider_class.pythondict2hash(s)).to eq({"key"=>"value", "key2"=>"value2"})
+        end
+      end
+
+      describe '#parsestring' do
+        it 'should call string2hash when provided with a string' do
+          s = "key='value', key2='value2'"
+          expect(provider_class.parsestring(s)).to eq({"key"=>"value", "key2"=>"value2"})
+        end
+
+        it 'should call pythondict2hash when provided with a hash' do
+          s = "{u'key': 'value', u'key2': 'value2'}"
+          expect(provider_class.parsestring(s)).to eq({"key"=>"value", "key2"=>"value2"})
+        end
+      end
+
     end
   end