From a92b39590f0b2036a99129deb2a5634046fc57aa Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:39:36 +0100 Subject: [PATCH 01/19] providers/minio_client_alias: Move to unwrap_maybe_sensitive to utils --- .../minio_client_alias/minio_client_alias.rb | 11 +++-------- lib/puppet_x/minio/util.rb | 13 +++++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 lib/puppet_x/minio/util.rb diff --git a/lib/puppet/provider/minio_client_alias/minio_client_alias.rb b/lib/puppet/provider/minio_client_alias/minio_client_alias.rb index fef206b..e119e21 100644 --- a/lib/puppet/provider/minio_client_alias/minio_client_alias.rb +++ b/lib/puppet/provider/minio_client_alias/minio_client_alias.rb @@ -3,6 +3,7 @@ require 'json' require 'puppet/resource_api/simple_provider' require 'puppet_x/minio/client' +require 'puppet_x/minio/util' LEGACY_PATH_SUPPORT_MAP ||= { '': 'auto', @@ -12,6 +13,8 @@ # Implementation for the minio_client_alias type using the Resource API. class Puppet::Provider::MinioClientAlias::MinioClientAlias < Puppet::ResourceApi::SimpleProvider + include PuppetX::Minio::Util + def get(context) context.debug('Returning list of minio client aliases') return [] unless PuppetX::Minio::Client.installed? @@ -80,12 +83,4 @@ def to_puppet_alias(json) path_lookup_support: path_lookup_support, } end - - def unwrap_maybe_sensitive(param) - if param.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) - return param.unwrap - end - - param - end end diff --git a/lib/puppet_x/minio/util.rb b/lib/puppet_x/minio/util.rb new file mode 100644 index 0000000..703b95a --- /dev/null +++ b/lib/puppet_x/minio/util.rb @@ -0,0 +1,13 @@ +module PuppetX + module Minio + module Util + def unwrap_maybe_sensitive(param) + if param.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + return param.unwrap + end + + param + end + end + end +end From 065eb38f13bf3379de2c4a555583b723d5e51530 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 9 Nov 2021 11:27:13 +0100 Subject: [PATCH 02/19] Add minio_bucket provider --- .../provider/minio_bucket/minio_bucket.rb | 44 ++++++++++ lib/puppet/type/minio_bucket.rb | 31 +++++++ .../minio_bucket/minio_bucket_spec.rb | 84 +++++++++++++++++++ spec/unit/puppet/type/minio_bucket_spec.rb | 10 +++ 4 files changed, 169 insertions(+) create mode 100644 lib/puppet/provider/minio_bucket/minio_bucket.rb create mode 100644 lib/puppet/type/minio_bucket.rb create mode 100644 spec/unit/puppet/provider/minio_bucket/minio_bucket_spec.rb create mode 100644 spec/unit/puppet/type/minio_bucket_spec.rb diff --git a/lib/puppet/provider/minio_bucket/minio_bucket.rb b/lib/puppet/provider/minio_bucket/minio_bucket.rb new file mode 100644 index 0000000..b3006d5 --- /dev/null +++ b/lib/puppet/provider/minio_bucket/minio_bucket.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'puppet/resource_api/simple_provider' +require 'puppet_x/minio/client' + +DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze + +# Implementation for the minio_bucket type using the Resource API. +class Puppet::Provider::MinioBucket::MinioBucket < Puppet::ResourceApi::SimpleProvider + def get(context) + context.debug('Returning list of minio buckets') + return [] unless PuppetX::Minio::Client.installed? + + @instances = [] + PuppetX::Minio::Client.execute("ls #{DEFAUlT_TARGET_ALIAS}").each do |json_bucket| + @instances << to_puppet_bucket(json_bucket) + end + @instances + end + + def create(context, name, should) + context.notice("Creating '#{name}' with #{should.inspect}") + PuppetX::Minio::Client.execute("mb #{DEFAUlT_TARGET_ALIAS}/#{name}") + end + + def update(context, name, should) + context.warning('`update` method not implemented for `minio_bucket` provider') + end + + def delete(context, name) + context.notice("Deleting '#{name}'") + PuppetX::Minio::Client.execute("rb --force #{DEFAUlT_TARGET_ALIAS}/#{name}") + end + + def to_puppet_bucket(json) + # Delete trailing slashes from bucket name + name = json['key'].chomp('/') + + { + ensure: 'present', + name: name, + } + end +end diff --git a/lib/puppet/type/minio_bucket.rb b/lib/puppet/type/minio_bucket.rb new file mode 100644 index 0000000..926e557 --- /dev/null +++ b/lib/puppet/type/minio_bucket.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'puppet/resource_api' + +Puppet::ResourceApi.register_type( + name: 'minio_bucket', + docs: <<-EOS, +@summary Manages local MinIO S3 buckets +@example + minio_bucket { 'my-bucket': + ensure => 'present', + } + +**Autorequires**: +* `File[/root/.minioclient]` +* `Minio_client_alias[puppet]` +EOS + features: [], + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this resource should be present or absent on the target system.', + default: 'present', + }, + name: { + type: 'String', + desc: 'The name of the resource you want to manage.', + behaviour: :namevar, + }, + }, +) diff --git a/spec/unit/puppet/provider/minio_bucket/minio_bucket_spec.rb b/spec/unit/puppet/provider/minio_bucket/minio_bucket_spec.rb new file mode 100644 index 0000000..38c139e --- /dev/null +++ b/spec/unit/puppet/provider/minio_bucket/minio_bucket_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +ensure_module_defined('Puppet::Provider::MinioBucket') +require 'puppet/provider/minio_bucket/minio_bucket' + +RSpec.describe Puppet::Provider::MinioBucket::MinioBucket do + subject(:provider) { described_class.new } + + let(:context) { instance_double('Puppet::ResourceApi::BaseContext', 'context') } + + before :each do + allow(context).to receive(:debug) + allow(context).to receive(:notice) + allow(context).to receive(:warning) + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:readlink).and_return('/usr/local/sbin/minio-client') + Puppet::Util::ExecutionStub.set do |_command, _options| + '' + end + end + + describe '#get' do + it 'returns empty list when client is not installed' do + allow(File).to receive(:exist?).and_return(false) + expect(provider.get(context)).to eq [] + end + + it 'processes resources' do + Puppet::Util::ExecutionStub.set do |_command, _options| + <<-JSONSTRINGS + {"status": "success","type": "folder","lastModified": "1970-01-01T00:00:00.000+01:00","size": 0,"key": "bucket-one/","etag": "","url": "http://localhost:9200","versionOrdinal": 1} + {"status": "success","type": "folder","lastModified": "1970-01-01T00:00:00.000+01:00","size": 0,"key": "bucket-two/","etag": "","url": "http://localhost:9200","versionOrdinal": 1} + JSONSTRINGS + end + + expect(context).to receive(:debug).with('Returning list of minio buckets') + expect(provider.get(context)).to eq [ + { + name: 'bucket-one', + ensure: 'present', + }, + { + name: 'bucket-two', + ensure: 'present', + }, + ] + end + end + + describe 'create(context, name, should)' do + it 'creates the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json mb puppet/bucket-one' + + '' + end + + expect(context).to receive(:notice).with(%r{\ACreating 'bucket-one'}) + provider.create(context, 'bucket-one', name: 'bucket-one', ensure: 'present') + end + end + + describe 'update(context, name, should)' do + it 'updates the resource' do + expect(context).to receive(:warning).with('`update` method not implemented for `minio_bucket` provider') + provider.update(context, 'bucket-one', name: 'bucket-one-test', ensure: 'present') + end + end + + describe 'delete(context, name)' do + it 'deletes the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json rb --force puppet/bucket-one' + + '' + end + + expect(context).to receive(:notice).with(%r{\ADeleting 'bucket-one'}) + provider.delete(context, 'bucket-one') + end + end +end diff --git a/spec/unit/puppet/type/minio_bucket_spec.rb b/spec/unit/puppet/type/minio_bucket_spec.rb new file mode 100644 index 0000000..f349ba7 --- /dev/null +++ b/spec/unit/puppet/type/minio_bucket_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'puppet/type/minio_bucket' + +RSpec.describe 'the minio_bucket type' do + it 'loads' do + expect(Puppet::Type.type(:minio_bucket)).not_to be_nil + end +end From 4f65e40d75978b10cb5ae46078347777779f83f8 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 9 Nov 2021 14:43:08 +0100 Subject: [PATCH 03/19] Add minio_user provider --- lib/puppet/provider/minio_user/minio_user.rb | 74 ++++++++++ lib/puppet/type/minio_user.rb | 46 ++++++ .../provider/minio_user/minio_user_spec.rb | 133 ++++++++++++++++++ spec/unit/puppet/type/minio_user_spec.rb | 10 ++ 4 files changed, 263 insertions(+) create mode 100644 lib/puppet/provider/minio_user/minio_user.rb create mode 100644 lib/puppet/type/minio_user.rb create mode 100644 spec/unit/puppet/provider/minio_user/minio_user_spec.rb create mode 100644 spec/unit/puppet/type/minio_user_spec.rb diff --git a/lib/puppet/provider/minio_user/minio_user.rb b/lib/puppet/provider/minio_user/minio_user.rb new file mode 100644 index 0000000..8029cb9 --- /dev/null +++ b/lib/puppet/provider/minio_user/minio_user.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'puppet/resource_api/simple_provider' +require 'puppet_x/minio/client' +require 'puppet_x/minio/util' + +DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze + +# Implementation for the minio_user type using the Resource API. +class Puppet::Provider::MinioUser::MinioUser < Puppet::ResourceApi::SimpleProvider + include PuppetX::Minio::Util + + def get(context) + context.debug('Returning list of minio users') + return [] unless PuppetX::Minio::Client.installed? + + @instances = [] + PuppetX::Minio::Client.execute("admin user list #{DEFAUlT_TARGET_ALIAS}").each do |json_user| + # `mcli admin user info` returns an array + json_user_info = PuppetX::Minio::Client.execute("admin user info #{DEFAUlT_TARGET_ALIAS} #{json_user['accessKey']}").first + @instances << to_puppet_user(json_user_info) + end + @instances + end + + def create(context, name, should) + context.notice("Creating '#{name}' with #{should.inspect}") + + operations = [] + operations << ["admin user add #{DEFAUlT_TARGET_ALIAS} #{should[:access_key]} #{unwrap_maybe_sensitive(should[:secret_key])}", sensitive: true] + operations << ["admin policy set #{DEFAUlT_TARGET_ALIAS} #{should[:policies].join(' ')} user=#{name}"] unless should[:policies].nil? + + operations.each do |op| + PuppetX::Minio::Client.execute(*op) + end + end + + def update(context, name, should) + context.notice("Updating '#{name}' with #{should.inspect}") + + operations = [] + operations << "admin policy set #{DEFAUlT_TARGET_ALIAS} #{should[:policies].join(' ')} user=#{name}" unless should[:policies].nil? + + operations.each do |op| + PuppetX::Minio::Client.execute(op) + end + end + + def delete(context, name) + context.notice("Deleting '#{name}'") + PuppetX::Minio::Client.execute("admin user remove #{DEFAUlT_TARGET_ALIAS} #{name}") + end + + def insync?(context, _name, property_name, is_hash, should_hash) + context.debug("Checking whether #{property_name} is out of sync") + case property_name + when :secret_key + # Let Puppet believe that the resource doesn't need updating, + # since we can't check a user's secret key + true + end + end + + def to_puppet_user(json) + policies = if json['policyName'].nil? then nil else json['policyName'].split(',') end + + { + ensure: 'present', + access_key: json['accessKey'], + policies: policies, + member_of: json['memberOf'] || [], + } + end +end diff --git a/lib/puppet/type/minio_user.rb b/lib/puppet/type/minio_user.rb new file mode 100644 index 0000000..09e40de --- /dev/null +++ b/lib/puppet/type/minio_user.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'puppet/resource_api' + +Puppet::ResourceApi.register_type( + name: 'minio_user', + docs: <<-EOS, +@summary Manages local MinIO users +@example + minio_user { 'userOne': + ensure => 'present', + secret_key => Sensitive('password'), + policies => ['consoleAdmin', 'custom-policy'], + } + +**Autorequires**: +* `File[/root/.minioclient]` +* `Minio_client_alias[puppet]` +EOS + features: ['custom_insync'], + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this resource should be present or absent on the target system.', + default: 'present', + }, + access_key: { + type: 'String', + desc: 'The API access key', + behaviour: :namevar, + }, + secret_key: { + type: 'Variant[Sensitive[String], String]', + desc: 'The API access secret', + }, + policies: { + type: 'Optional[Array[String]]', + desc: 'List of MinIO PBAC policies to set for this user.', + }, + member_of: { + type: 'Optional[Array[String]]', + desc: 'List of groups the user is a member of.', + behaviour: :read_only, + }, + }, +) diff --git a/spec/unit/puppet/provider/minio_user/minio_user_spec.rb b/spec/unit/puppet/provider/minio_user/minio_user_spec.rb new file mode 100644 index 0000000..c5f63ff --- /dev/null +++ b/spec/unit/puppet/provider/minio_user/minio_user_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' + +ensure_module_defined('Puppet::Provider::MinioUser') +require 'puppet/provider/minio_user/minio_user' + +RSpec.describe Puppet::Provider::MinioUser::MinioUser do + subject(:provider) { described_class.new } + + let(:context) { instance_double('Puppet::ResourceApi::BaseContext', 'context') } + + before :each do + allow(context).to receive(:debug) + allow(context).to receive(:notice) + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:readlink).and_return('/usr/local/sbin/minio-client') + Puppet::Util::ExecutionStub.set do |_command, _options| + '' + end + end + + describe '#get' do + it 'returns empty list when client is not installed' do + allow(File).to receive(:exist?).and_return(false) + expect(provider.get(context)).to eq [] + end + + it 'processes resources' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /list/ + out = <<-JSONSTRINGS + {"status": "success","accessKey": "user-one","userStatus": "enabled"} + {"status": "success","accessKey": "user-two","userStatus": "enabled"} + JSONSTRINGS + when /user-one/ + out = <<-JSONSTRINGS + {"status": "success","accessKey": "user-one","userStatus": "enabled","memberOf": ["group-one"]} + JSONSTRINGS + else + out = <<-JSONSTRINGS + {"status": "success","accessKey": "user-two","policyName": "readonly,custom-policy","userStatus": "enabled"} + JSONSTRINGS + end + + out + end + + expect(context).to receive(:debug).with('Returning list of minio users') + expect(provider.get(context)).to eq [ + { + access_key: 'user-one', + ensure: 'present', + policies: nil, + member_of: ['group-one'], + }, + { + access_key: 'user-two', + ensure: 'present', + policies: ['readonly','custom-policy'], + member_of: [], + }, + ] + end + end + + describe 'create(context, name, should)' do + it 'with defaults' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json admin user add puppet user-one MySecretPass' + + '' + end + expect(context).to receive(:notice).with(%r{\ACreating 'user-one'}) + + provider.create(context, 'user-one', + ensure: 'present', + access_key: 'user-one', + secret_key: Puppet::Pops::Types::PSensitiveType::Sensitive.new('MySecretPass')) + end + + it 'with policies' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /policy/ + expected_command = '/usr/local/sbin/minio-client --json admin policy set puppet readonly custom-policy user=user-one' + else + expected_command = '/usr/local/sbin/minio-client --json admin user add puppet user-one MySecretPass' + end + expect(command).to eq expected_command + + '' + end + expect(context).to receive(:notice).with(%r{\ACreating 'user-one'}) + + provider.create(context, 'user-one', + ensure: 'present', + access_key: 'user-one', + secret_key: Puppet::Pops::Types::PSensitiveType::Sensitive.new('MySecretPass'), + policies: ['readonly', 'custom-policy']) + end + end + + describe 'update(context, name, should)' do + it 'updates the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json admin policy set puppet consoleAdmin user=user-one' + + '' + end + expect(context).to receive(:notice).with(%r{\AUpdating 'user-one'}) + + provider.update(context, 'user-one', + ensure: 'present', + access_key: 'user-one', + policies: ['consoleAdmin']) + end + end + + describe 'delete(context, name)' do + it 'deletes the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json admin user remove puppet user-one' + + '' + end + expect(context).to receive(:notice).with(%r{\ADeleting 'user-one'}) + + provider.delete(context, 'user-one') + end + end +end diff --git a/spec/unit/puppet/type/minio_user_spec.rb b/spec/unit/puppet/type/minio_user_spec.rb new file mode 100644 index 0000000..fb7714e --- /dev/null +++ b/spec/unit/puppet/type/minio_user_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'puppet/type/minio_user' + +RSpec.describe 'the minio_user type' do + it 'loads' do + expect(Puppet::Type.type(:minio_user)).not_to be_nil + end +end From c497a9c89a47f985836ff661124f208d66d00cc2 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Wed, 10 Nov 2021 11:40:23 +0100 Subject: [PATCH 04/19] Add minio_group provider --- .../provider/minio_group/minio_group.rb | 78 ++++++++ lib/puppet/type/minio_group.rb | 52 ++++++ .../provider/minio_group/minio_group_spec.rb | 174 ++++++++++++++++++ spec/unit/puppet/type/minio_group_spec.rb | 10 + 4 files changed, 314 insertions(+) create mode 100644 lib/puppet/provider/minio_group/minio_group.rb create mode 100644 lib/puppet/type/minio_group.rb create mode 100644 spec/unit/puppet/provider/minio_group/minio_group_spec.rb create mode 100644 spec/unit/puppet/type/minio_group_spec.rb diff --git a/lib/puppet/provider/minio_group/minio_group.rb b/lib/puppet/provider/minio_group/minio_group.rb new file mode 100644 index 0000000..b19fce9 --- /dev/null +++ b/lib/puppet/provider/minio_group/minio_group.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'puppet/resource_api/simple_provider' +require 'puppet_x/minio/client' + +DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze +GROUP_STATUS_MAP ||= { + 'enabled': true, + 'disabled': false, +}.freeze + +# Implementation for the minio_group type using the Resource API. +class Puppet::Provider::MinioGroup::MinioGroup < Puppet::ResourceApi::SimpleProvider + def get(context) + context.debug('Returning list of minio groups') + return [] unless PuppetX::Minio::Client.installed? + + # `mcli admin group list` returns an array + json_groups = PuppetX::Minio::Client.execute("admin group list #{DEFAUlT_TARGET_ALIAS}").first + return [] unless json_groups.key?('groups') + + @instances = [] + json_groups['groups'].each do |group| + # `mcli admin group info` returns an array + json_group_info = PuppetX::Minio::Client.execute("admin group info #{DEFAUlT_TARGET_ALIAS} #{group}").first + @instances << to_puppet_group(json_group_info) + end + @instances + end + + def create(context, name, should) + context.notice("Creating '#{name}' with #{should.inspect}") + + operations = [] + operations << "admin group add #{DEFAUlT_TARGET_ALIAS} #{name} #{should[:members].join(' ')}" + + operations << "admin group disable #{DEFAUlT_TARGET_ALIAS} #{name}" unless should[:enabled] + operations << "admin policy set #{DEFAUlT_TARGET_ALIAS} #{should[:policies].join(',')} group=#{name}" unless should[:policies].nil? + + operations.each do |op| + PuppetX::Minio::Client.execute(op) + end + end + + def update(context, name, should) + context.notice("Updating '#{name}' with #{should.inspect}") + + # TODO: Do a proper update instead of recreating the group + delete(context, name) + create(context, name, should) + end + + def delete(context, name) + context.notice("Deleting '#{name}'") + + members = PuppetX::Minio::Client.execute("admin group info #{DEFAUlT_TARGET_ALIAS} #{name}").first['members'] + operations = [] + + operations << "admin group remove #{DEFAUlT_TARGET_ALIAS} #{name} #{members.join(' ')}" + operations << "admin group remove #{DEFAUlT_TARGET_ALIAS} #{name}" + + operations.each do |op| + PuppetX::Minio::Client.execute(op) + end + end + + def to_puppet_group(json) + policies = if json['groupPolicy'].nil? then nil else json['groupPolicy'].split(',') end + + { + ensure: 'present', + name: json['groupName'], + members: json['members'] || [], + policies: policies, + enabled: GROUP_STATUS_MAP[json['groupStatus'].to_sym], + } + end +end diff --git a/lib/puppet/type/minio_group.rb b/lib/puppet/type/minio_group.rb new file mode 100644 index 0000000..fbe5b53 --- /dev/null +++ b/lib/puppet/type/minio_group.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'puppet/resource_api' + +Puppet::ResourceApi.register_type( + name: 'minio_group', + docs: <<-EOS, +@summary Manages local MinIO groups +@example + minio_group { 'admins': + ensure => 'present', + members => ['userOne', 'userTwo'], + policies => ['consoleAdmin'], + } +@example + minio_group { 'my-group': + ensure => 'present', + members => ['userThree', 'userFour'], + policies => ['custom-policy'], + } + +**Autorequires**: +* `File[/root/.minioclient]` +* `Minio_client_alias[puppet]` +EOS + features: [], + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this resource should be present or absent on the target system.', + default: 'present', + }, + name: { + type: 'String', + desc: 'The name of the resource you want to manage.', + behaviour: :namevar, + }, + members: { + type: 'Array[String, 1]', + desc: 'List of users that should be part of this group.', + }, + enabled: { + type: 'Optional[Boolean]', + desc: 'Set to false to disable this group. Defaults to true.', + default: true, + }, + policies: { + type: 'Optional[Array[String]]', + desc: 'List of MinIO PBAC policies to set for this group.', + }, + }, +) diff --git a/spec/unit/puppet/provider/minio_group/minio_group_spec.rb b/spec/unit/puppet/provider/minio_group/minio_group_spec.rb new file mode 100644 index 0000000..5ecf3fe --- /dev/null +++ b/spec/unit/puppet/provider/minio_group/minio_group_spec.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require 'spec_helper' + +ensure_module_defined('Puppet::Provider::MinioGroup') +require 'puppet/provider/minio_group/minio_group' + +RSpec.describe Puppet::Provider::MinioGroup::MinioGroup do + subject(:provider) { described_class.new } + + let(:context) { instance_double('Puppet::ResourceApi::BaseContext', 'context') } + + before :each do + allow(context).to receive(:debug) + allow(context).to receive(:notice) + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:readlink).and_return('/usr/local/sbin/minio-client') + Puppet::Util::ExecutionStub.set do |_command, _options| + '' + end + end + + describe '#get' do + it 'returns empty list when client is not installed' do + allow(File).to receive(:exist?).and_return(false) + expect(provider.get(context)).to eq [] + end + + it 'processes resources' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /list/ + out = <<-JSONSTRINGS + {"status": "success","groups": ["test-one","test-two"]} + JSONSTRINGS + when /test-one/ + out = <<-JSONSTRINGS + {"status": "success","groupName": "test-one","members": ["user-one","user-two"],"groupStatus": "enabled","groupPolicy": "test-policy"} + JSONSTRINGS + else + out = <<-JSONSTRINGS + {"status": "success","groupName": "test-two","members": ["user-three","user-four"],"groupStatus": "enabled","groupPolicy": "consoleAdmin"} + JSONSTRINGS + end + + out + end + + expect(context).to receive(:debug).with('Returning list of minio groups') + expect(provider.get(context)).to eq [ + { + name: 'test-one', + ensure: 'present', + members: ['user-one', 'user-two'], + policies: ['test-policy'], + enabled: true, + }, + { + name: 'test-two', + ensure: 'present', + members: ['user-three', 'user-four'], + policies: ['consoleAdmin'], + enabled: true, + }, + ] + end + end + + describe 'create(context, name, should)' do + it 'with defaults' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json admin group add puppet test-one user-one user-two' + + '' + end + expect(context).to receive(:notice).with(%r{\ACreating 'test-one'}) + + provider.create(context, 'test-one', + name: 'test-one', + ensure: 'present', + members: ['user-one', 'user-two'], + enabled: true) + end + + it 'with policies set' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /policy/ + expected_command = '/usr/local/sbin/minio-client --json admin policy set puppet custom-policy-one,custom-policy-two group=test-one' + else + expected_command = '/usr/local/sbin/minio-client --json admin group add puppet test-one user-one user-two' + end + expect(command).to eq(expected_command) + + '' + end + expect(context).to receive(:notice).with (%r{\ACreating 'test-one'}) + + provider.create(context, 'test-one', + name: 'test-one', + ensure: 'present', + members: ['user-one', 'user-two'], + policies: ['custom-policy-one', 'custom-policy-two'], + enabled: true) + end + + it 'with group disabled' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /disable/ + expected_command = '/usr/local/sbin/minio-client --json admin group disable puppet test-one' + else + expected_command = '/usr/local/sbin/minio-client --json admin group add puppet test-one user-one user-two' + end + expect(command).to eq(expected_command) + + '' + end + expect(context).to receive(:notice).with (%r{\ACreating 'test-one'}) + + provider.create(context, 'test-one', + name: 'test-one', + ensure: 'present', + members: ['user-one', 'user-two'], + enabled: false) + + end + end + + describe 'update(context, name, should)' do + it 'updates the resource' do + expect(context).to receive(:notice).with(%r{\AUpdating 'test-one'}) + + expect(subject).to receive(:delete).with(context, 'test-one') + expect(subject).to receive(:create).with(context, 'test-one', + name: 'test-one', + ensure: 'present', + members: ['user-one'], + enabled: false) + + provider.update(context, 'test-one', + name: 'test-one', + ensure: 'present', + members: ['user-one'], + enabled: false) + end + end + + describe 'delete(context, name)' do + it 'deletes the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /info/ + expected_command = '/usr/local/sbin/minio-client --json admin group info puppet test-one' + out = <<-JSONSTRING + {"status": "success","groupName": "test-one","members": ["user-one","user-two"],"groupStatus": "enabled","groupPolicy": "test-policy"} + JSONSTRING + when /user/ + expected_command = '/usr/local/sbin/minio-client --json admin group remove puppet test-one user-one user-two' + out = '' + else + expected_command = '/usr/local/sbin/minio-client --json admin group remove puppet test-one' + out = '' + end + + expect(command).to eq(expected_command) + out + end + expect(context).to receive(:notice).with(%r{\ADeleting 'test-one'}) + + provider.delete(context, 'test-one') + end + end +end diff --git a/spec/unit/puppet/type/minio_group_spec.rb b/spec/unit/puppet/type/minio_group_spec.rb new file mode 100644 index 0000000..2e6822f --- /dev/null +++ b/spec/unit/puppet/type/minio_group_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'puppet/type/minio_group' + +RSpec.describe 'the minio_group type' do + it 'loads' do + expect(Puppet::Type.type(:minio_group)).not_to be_nil + end +end From 31a6abbf66db6c34988e7d3ff0f35440c7b1b751 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Wed, 17 Nov 2021 13:37:33 +0100 Subject: [PATCH 05/19] Add minio_policy provider --- .../provider/minio_policy/minio_policy.rb | 94 ++++++++++++ lib/puppet/type/minio_policy.rb | 52 +++++++ .../minio_policy/minio_policy_spec.rb | 144 ++++++++++++++++++ spec/unit/puppet/type/minio_policy_spec.rb | 10 ++ 4 files changed, 300 insertions(+) create mode 100644 lib/puppet/provider/minio_policy/minio_policy.rb create mode 100644 lib/puppet/type/minio_policy.rb create mode 100644 spec/unit/puppet/provider/minio_policy/minio_policy_spec.rb create mode 100644 spec/unit/puppet/type/minio_policy_spec.rb diff --git a/lib/puppet/provider/minio_policy/minio_policy.rb b/lib/puppet/provider/minio_policy/minio_policy.rb new file mode 100644 index 0000000..fbafc3c --- /dev/null +++ b/lib/puppet/provider/minio_policy/minio_policy.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'json' +require 'tempfile' + +require 'puppet/resource_api/simple_provider' +require 'puppet_x/minio/client' + +DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze +DEFAULT_POLICY_VERSION ||= '2012-10-17'.freeze + +# Implementation for the minio_policy type using the Resource API. +class Puppet::Provider::MinioPolicy::MinioPolicy < Puppet::ResourceApi::SimpleProvider + def get(context) + context.debug('Returning list of minio policies') + return [] unless PuppetX::Minio::Client.installed? + + json_policies = PuppetX::Minio::Client.execute("admin policy list #{DEFAUlT_TARGET_ALIAS}") + return [] if json_policies.empty? + + @instances = [] + json_policies.each do |policy| + # `mcli admin policy info` returns an array + json_policy_info = PuppetX::Minio::Client.execute("admin policy info #{DEFAUlT_TARGET_ALIAS} #{policy['policy']}").first + @instances << to_puppet_policy(json_policy_info) + end + @instances + end + + def create(context, name, should) + context.notice("Creating '#{name}' with #{should.inspect}") + + f = Tempfile.new(["#{name}-policy", '.json']) + begin + json_policy = Hash[:Version => DEFAULT_POLICY_VERSION, :Statement => should[:statement]].to_json + + f.write(json_policy) + f.rewind + + PuppetX::Minio::Client.execute("admin policy add #{DEFAUlT_TARGET_ALIAS} #{name} #{f.path}") + ensure + f.close + f.unlink + end + end + + def update(context, name, should) + context.notice("Updating '#{name}' with #{should.inspect}") + + # There's currently no way to update an existing policy via the client, + # so delete and recreate the policy + delete(context, name) + create(context, name, should) + end + + def delete(context, name) + context.notice("Deleting '#{name}'") + PuppetX::Minio::Client.execute("admin policy remove #{DEFAUlT_TARGET_ALIAS} #{name}") + end + + def to_puppet_policy(json) + statements = [] + json['policyJSON']['Statement'].each do |s| + statements << sanitize_statement(s) + end + + { + ensure: 'present', + name: json['policy'], + version: json['policyJSON']['Version'], + statement: statements, + } + end + + def sanitize_statement(statement) + statement.transform_keys!(&:capitalize) + + [:Action, :Resource].each do |k| + statement[k].sort! unless statement[k].nil? + end + + statement + end + + def canonicalize(_context, resources) + resources.each do |r| + unless r[:statement].nil? + r[:statement].each do |s| + s = sanitize_statement(s) + end + end + end + end +end diff --git a/lib/puppet/type/minio_policy.rb b/lib/puppet/type/minio_policy.rb new file mode 100644 index 0000000..db8c2aa --- /dev/null +++ b/lib/puppet/type/minio_policy.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'puppet/resource_api' + +Puppet::ResourceApi.register_type( + name: 'minio_policy', + docs: <<-EOS, +@summary Manages local MinIO policies +@example + minio_policy { 'custom-policy': + ensure => 'present', + statement => [ + { + 'Effect' => 'Allow', + 'Action' => ['s3:ListBucket'], + 'Resource' => ['arn:aws:s3:::my-bucket'] + }, + { + 'Effect' => 'Allow', + 'Action' => ['s3:GetObject', 's3:PutObject'], + 'Resource' => ['arn:aws:s3:::my-bucket'] + } + ], + } + +**Autorequires**: +* `File[/root/.minioclient]` +* `Minio_client_alias[puppet]` +EOS + features: ['canonicalize'], + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this resource should be present or absent on the target system.', + default: 'present', + }, + name: { + type: 'String', + desc: 'The name of the resource you want to manage.', + behaviour: :namevar, + }, + version: { + type: 'String', + desc: 'Specifies the language syntax rules that are to be used to process a policy.', + behaviour: :read_only, + }, + statement: { + type: 'Array[Hash]', + desc: 'List of statements describing the policy.', + } + }, +) diff --git a/spec/unit/puppet/provider/minio_policy/minio_policy_spec.rb b/spec/unit/puppet/provider/minio_policy/minio_policy_spec.rb new file mode 100644 index 0000000..828bbe3 --- /dev/null +++ b/spec/unit/puppet/provider/minio_policy/minio_policy_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +ensure_module_defined('Puppet::Provider::MinioPolicy') +require 'puppet/provider/minio_policy/minio_policy' + +RSpec.describe Puppet::Provider::MinioPolicy::MinioPolicy do + subject(:provider) { described_class.new } + + let(:context) { instance_double('Puppet::ResourceApi::BaseContext', 'context') } + + before :each do + allow(context).to receive(:debug) + allow(context).to receive(:notice) + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:readlink).and_return('/usr/local/sbin/minio-client') + Puppet::Util::ExecutionStub.set do |_command, _options| + '' + end + end + + describe '#get' do + it 'returns empty list when client is not installed' do + allow(File).to receive(:exist?).and_return(false) + expect(provider.get(context)).to eq [] + end + + it 'processes resources' do + Puppet::Util::ExecutionStub.set do |command, _options| + case command + when /list/ + out = <<-JSONSTRINGS + {"status": "success","policy": "test-one","isGroup": false} + JSONSTRINGS + else + out = <<-JSONSTRINGS + {"status": "success","policy": "test-one","isGroup": false,"policyJSON": {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": ["s3:ListBucket"],"Resource": ["arn:aws:s3:::test-one-bucket-*"]},{"Effect": "Allow","Action": ["s3:GetObject","s3:PutObject"],"Resource": ["arn:aws:s3:::test-one-*"]}]}} + JSONSTRINGS + end + + out + end + + expect(context).to receive(:debug).with('Returning list of minio policies') + expect(provider.get(context)).to eq [ + { + name: 'test-one', + ensure: 'present', + version: '2012-10-17', + statement: [ # Using hash rockets, since Puppet returns strings as hash keys + { + 'Effect' => 'Allow', + 'Action' => ['s3:ListBucket'], + 'Resource' => ['arn:aws:s3:::test-one-bucket-*'], + }, + { + 'Effect' => 'Allow', + 'Action' => ['s3:GetObject','s3:PutObject'], + 'Resource' => ['arn:aws:s3:::test-one-*'], + }, + ], + }, + ] + end + end + + describe 'create(context, name, should)' do + it 'creates the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to include '/usr/local/sbin/minio-client --json admin policy add puppet test-one' + + '' + end + expect(context).to receive(:notice).with(%r{\ACreating 'test-one'}) + + provider.create(context, 'test-one', + name: 'test-one', + ensure: 'present', + statement: [ + { + Effect: 'Allow', + Action:['s3:ListBucket'], + Resource: ['arn:aws:s3:::test-one-bucket-*'], + } + ]) + end + end + + describe 'update(context, name, should)' do + it 'updates the resource' do + expect(context).to receive(:notice).with(%r{\AUpdating 'test-one'}) + + expect(subject).to receive(:delete).with(context, 'test-one') + expect(subject).to receive(:create).with(context, 'test-one', + name: 'test-one', + ensure: 'present', + statement: [ + { + Effect: 'Allow', + Action:['s3:ListBucket'], + Resource: ['arn:aws:s3:::test-one-bucket-*'], + } + ]) + + provider.update(context, 'test-one', + name: 'test-one', + ensure: 'present', + statement: [ + { + Effect: 'Allow', + Action:['s3:ListBucket'], + Resource: ['arn:aws:s3:::test-one-bucket-*'], + } + ]) + end + end + + describe 'delete(context, name)' do + it 'deletes the resource' do + Puppet::Util::ExecutionStub.set do |command, _options| + expect(command).to eq '/usr/local/sbin/minio-client --json admin policy remove puppet test-one' + + '' + end + expect(context).to receive(:notice).with(%r{\ADeleting 'test-one'}) + + provider.delete(context, 'test-one') + end + end + + describe 'sanitize_statement(statement)' do + def self.test_sanitize_statement(desc, input) + expected = {:Effect => 'Allow', :Action => ['s3:GetObject', 's3:PutObject'], :Resource => ['arn:aws:s3:::test-one-bucket-*']} + + it desc do + expect(provider.sanitize_statement(input)).to eq expected + end + end + + test_sanitize_statement 'capitalizes keys', {'effect': 'Allow', 'action': ['s3:GetObject', 's3:PutObject'], 'resource': ['arn:aws:s3:::test-one-bucket-*']} + test_sanitize_statement 'sorts Action and Resource arrays', {'Effect': 'Allow', 'Action': ['s3:PutObject', 's3:GetObject'], 'Resource': ['arn:aws:s3:::test-one-bucket-*']} + end +end diff --git a/spec/unit/puppet/type/minio_policy_spec.rb b/spec/unit/puppet/type/minio_policy_spec.rb new file mode 100644 index 0000000..88880e9 --- /dev/null +++ b/spec/unit/puppet/type/minio_policy_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'puppet/type/minio_policy' + +RSpec.describe 'the minio_policy type' do + it 'loads' do + expect(Puppet::Type.type(:minio_policy)).not_to be_nil + end +end From bb4395e376730960f63124070c87dfa4e90e519a Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 5 Apr 2022 17:26:53 +0200 Subject: [PATCH 06/19] manifests: Add parameter for setting a default client alias --- manifests/client.pp | 3 +++ manifests/client/config.pp | 11 +++++++++++ manifests/init.pp | 3 +++ 3 files changed, 17 insertions(+) diff --git a/manifests/client.pp b/manifests/client.pp index e25309a..9ac357f 100644 --- a/manifests/client.pp +++ b/manifests/client.pp @@ -40,6 +40,8 @@ # Target directory to hold the minio client installation. Default: `/opt/minioclient` # @param [Hash] aliases # List of aliases to add to the minio client configuration. For parameter description see `minio_client_alias`. +# @param [String] default_client_alias +# The default client alias to use when interacting with MinIO's API. Required. # @param [Boolean] purge_unmanaged_aliases # Decides if puppet should purge unmanaged minio client aliases # @@ -56,6 +58,7 @@ Stdlib::Absolutepath $installation_directory = $minio::client_installation_directory, String $binary_name = $minio::client_binary_name, Hash $aliases = $minio::client_aliases, + String $default_client_alias = $minio::default_client_alias, Boolean $purge_unmanaged_aliases = $minio::purge_unmanaged_client_aliases, ) { if ($manage_client_installation) { diff --git a/manifests/client/config.pp b/manifests/client/config.pp index ad44e86..aeac5c6 100644 --- a/manifests/client/config.pp +++ b/manifests/client/config.pp @@ -18,6 +18,8 @@ # # @param [Hash] aliases # List of aliases to add to the minio client configuration. For parameter description see `minio_client_alias`. +# @param [String] default_client_alias +# The default client alias to use when interacting with MinIO's API. Required. # @param [Boolean] purge_unmanaged_aliases # Decides if puppet should purge unmanaged minio client aliases # @@ -26,6 +28,7 @@ # class minio::client::config( Hash $aliases = $minio::client::aliases, + String $default_client_alias = $minio::client::default_client_alias, Boolean $purge_unmanaged_aliases = $minio::client::purge_unmanaged_aliases, ) { if ($purge_unmanaged_aliases) { @@ -34,6 +37,14 @@ } } + file { '/root/.minio_default_alias': + ensure => file, + owner => 'root', + group => 'root', + mode => '0640', + content => $default_client_alias, + } + $aliases.each | $alias, $alias_values | { minio_client_alias {$alias: * => $alias_values, diff --git a/manifests/init.pp b/manifests/init.pp index acceb4a..eec75cc 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -114,6 +114,8 @@ # List of aliases to add to the minio client configuration. For parameter description see `minio_client_alias`. # @param [Boolean] purge_unmanaged_client_aliases # Decides if puppet should purge unmanaged minio client aliases +# @param [String] default_client_alias +# The default client alias to use when interacting with MinIO's API. Required. # @param [Enum['present', 'absent']] cert_ensure # Decides if minio certificates binary will be installed. # @param [Stdlib::Absolutepath] cert_directory @@ -178,6 +180,7 @@ String $client_binary_name, Hash $client_aliases, Boolean $purge_unmanaged_client_aliases, + String $default_client_alias, Enum['present', 'absent'] $cert_ensure, Stdlib::Absolutepath $cert_directory, Optional[String[1]] $default_cert_name, From b6e4b054780a748fa9305803d6a9c6794f227d9a Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 5 Apr 2022 18:52:28 +0200 Subject: [PATCH 07/19] lib/puppet_x/minio/client: Add helper method for using default alias --- lib/puppet_x/minio/client.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/puppet_x/minio/client.rb b/lib/puppet_x/minio/client.rb index 161c5f0..9bb83fe 100644 --- a/lib/puppet_x/minio/client.rb +++ b/lib/puppet_x/minio/client.rb @@ -6,8 +6,10 @@ module PuppetX # rubocop:disable Style/ClassAndModuleChildren module Minio # rubocop:disable Style/ClassAndModuleChildren class Client # rubocop:disable Style/Documentation CLIENT_LINK_LOCATION = '/root/.minioclient' + DEFAULT_ALIAS_LOCATION = '/root/.minio_default_alias' @client_location = nil + @default_alias = nil def self.execute(args, **execute_args) ensure_client_installed @@ -19,6 +21,11 @@ def self.execute(args, **execute_args) end end + def self.alias + ensure_alias + @default_alias + end + def self.ensure_client_installed return if @client_location @@ -33,9 +40,27 @@ def self.ensure_client_installed @client_location = File.readlink(CLIENT_LINK_LOCATION) end + def self.ensure_alias + return if @default_alias + + unless alias_set? + errormsg = [ + "MinIO default alias file does not exist at #{DEFAULT_ALIAS_LOCATION}. ", + 'Make sure to specify an alias to be used with `minio::default_client_alias`.', + ] + raise Puppet::ExecutionFailure, errormsg.join + end + + @default_alias = File.read(DEFAULT_ALIAS_LOCATION) + end + def self.installed? File.exist?(CLIENT_LINK_LOCATION) end + + def self.alias_set? + File.exist?(DEFAULT_ALIAS_LOCATION) + end end end end From 7709e88a6f8002d43f3990a6186a4f10b4eb8ff9 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 5 Apr 2022 19:48:01 +0200 Subject: [PATCH 08/19] providers: Use helper alias method from client Instead of hardcoding the alias. --- .../provider/minio_bucket/minio_bucket.rb | 14 ++++++----- .../provider/minio_group/minio_group.rb | 23 +++++++++++-------- .../provider/minio_policy/minio_policy.rb | 15 +++++++----- lib/puppet/provider/minio_user/minio_user.rb | 20 ++++++++-------- lib/puppet/type/minio_bucket.rb | 2 +- lib/puppet/type/minio_group.rb | 2 +- lib/puppet/type/minio_policy.rb | 2 +- lib/puppet/type/minio_user.rb | 2 +- 8 files changed, 45 insertions(+), 35 deletions(-) diff --git a/lib/puppet/provider/minio_bucket/minio_bucket.rb b/lib/puppet/provider/minio_bucket/minio_bucket.rb index b3006d5..fcff22c 100644 --- a/lib/puppet/provider/minio_bucket/minio_bucket.rb +++ b/lib/puppet/provider/minio_bucket/minio_bucket.rb @@ -3,16 +3,18 @@ require 'puppet/resource_api/simple_provider' require 'puppet_x/minio/client' -DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze - # Implementation for the minio_bucket type using the Resource API. class Puppet::Provider::MinioBucket::MinioBucket < Puppet::ResourceApi::SimpleProvider + def initialize + @alias = PuppetX::Minio::Client.alias + end + def get(context) context.debug('Returning list of minio buckets') - return [] unless PuppetX::Minio::Client.installed? + return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? @instances = [] - PuppetX::Minio::Client.execute("ls #{DEFAUlT_TARGET_ALIAS}").each do |json_bucket| + PuppetX::Minio::Client.execute("ls #{@alias}").each do |json_bucket| @instances << to_puppet_bucket(json_bucket) end @instances @@ -20,7 +22,7 @@ def get(context) def create(context, name, should) context.notice("Creating '#{name}' with #{should.inspect}") - PuppetX::Minio::Client.execute("mb #{DEFAUlT_TARGET_ALIAS}/#{name}") + PuppetX::Minio::Client.execute("mb #{@alias}/#{name}") end def update(context, name, should) @@ -29,7 +31,7 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - PuppetX::Minio::Client.execute("rb --force #{DEFAUlT_TARGET_ALIAS}/#{name}") + PuppetX::Minio::Client.execute("rb --force #{@alias}/#{name}") end def to_puppet_bucket(json) diff --git a/lib/puppet/provider/minio_group/minio_group.rb b/lib/puppet/provider/minio_group/minio_group.rb index b19fce9..960105e 100644 --- a/lib/puppet/provider/minio_group/minio_group.rb +++ b/lib/puppet/provider/minio_group/minio_group.rb @@ -3,7 +3,6 @@ require 'puppet/resource_api/simple_provider' require 'puppet_x/minio/client' -DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze GROUP_STATUS_MAP ||= { 'enabled': true, 'disabled': false, @@ -11,18 +10,22 @@ # Implementation for the minio_group type using the Resource API. class Puppet::Provider::MinioGroup::MinioGroup < Puppet::ResourceApi::SimpleProvider + def initialize + @alias = PuppetX::Minio::Client.alias + end + def get(context) context.debug('Returning list of minio groups') - return [] unless PuppetX::Minio::Client.installed? + return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? # `mcli admin group list` returns an array - json_groups = PuppetX::Minio::Client.execute("admin group list #{DEFAUlT_TARGET_ALIAS}").first + json_groups = PuppetX::Minio::Client.execute("admin group list #{@alias}").first return [] unless json_groups.key?('groups') @instances = [] json_groups['groups'].each do |group| # `mcli admin group info` returns an array - json_group_info = PuppetX::Minio::Client.execute("admin group info #{DEFAUlT_TARGET_ALIAS} #{group}").first + json_group_info = PuppetX::Minio::Client.execute("admin group info #{@alias} #{group}").first @instances << to_puppet_group(json_group_info) end @instances @@ -32,10 +35,10 @@ def create(context, name, should) context.notice("Creating '#{name}' with #{should.inspect}") operations = [] - operations << "admin group add #{DEFAUlT_TARGET_ALIAS} #{name} #{should[:members].join(' ')}" + operations << "admin group add #{@alias} #{name} #{should[:members].join(' ')}" - operations << "admin group disable #{DEFAUlT_TARGET_ALIAS} #{name}" unless should[:enabled] - operations << "admin policy set #{DEFAUlT_TARGET_ALIAS} #{should[:policies].join(',')} group=#{name}" unless should[:policies].nil? + operations << "admin group disable #{@alias} #{name}" unless should[:enabled] + operations << "admin policy set #{@alias} #{should[:policies].join(',')} group=#{name}" unless should[:policies].nil? operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -53,11 +56,11 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - members = PuppetX::Minio::Client.execute("admin group info #{DEFAUlT_TARGET_ALIAS} #{name}").first['members'] + members = PuppetX::Minio::Client.execute("admin group info #{@alias} #{name}").first['members'] operations = [] - operations << "admin group remove #{DEFAUlT_TARGET_ALIAS} #{name} #{members.join(' ')}" - operations << "admin group remove #{DEFAUlT_TARGET_ALIAS} #{name}" + operations << "admin group remove #{@alias} #{name} #{members.join(' ')}" + operations << "admin group remove #{@alias} #{name}" operations.each do |op| PuppetX::Minio::Client.execute(op) diff --git a/lib/puppet/provider/minio_policy/minio_policy.rb b/lib/puppet/provider/minio_policy/minio_policy.rb index fbafc3c..ea0e6ac 100644 --- a/lib/puppet/provider/minio_policy/minio_policy.rb +++ b/lib/puppet/provider/minio_policy/minio_policy.rb @@ -6,22 +6,25 @@ require 'puppet/resource_api/simple_provider' require 'puppet_x/minio/client' -DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze DEFAULT_POLICY_VERSION ||= '2012-10-17'.freeze # Implementation for the minio_policy type using the Resource API. class Puppet::Provider::MinioPolicy::MinioPolicy < Puppet::ResourceApi::SimpleProvider + def initialize + @alias = PuppetX::Minio::Client.alias + end + def get(context) context.debug('Returning list of minio policies') - return [] unless PuppetX::Minio::Client.installed? + return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? - json_policies = PuppetX::Minio::Client.execute("admin policy list #{DEFAUlT_TARGET_ALIAS}") + json_policies = PuppetX::Minio::Client.execute("admin policy list #{@alias}") return [] if json_policies.empty? @instances = [] json_policies.each do |policy| # `mcli admin policy info` returns an array - json_policy_info = PuppetX::Minio::Client.execute("admin policy info #{DEFAUlT_TARGET_ALIAS} #{policy['policy']}").first + json_policy_info = PuppetX::Minio::Client.execute("admin policy info #{@alias} #{policy['policy']}").first @instances << to_puppet_policy(json_policy_info) end @instances @@ -37,7 +40,7 @@ def create(context, name, should) f.write(json_policy) f.rewind - PuppetX::Minio::Client.execute("admin policy add #{DEFAUlT_TARGET_ALIAS} #{name} #{f.path}") + PuppetX::Minio::Client.execute("admin policy add #{@alias} #{name} #{f.path}") ensure f.close f.unlink @@ -55,7 +58,7 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - PuppetX::Minio::Client.execute("admin policy remove #{DEFAUlT_TARGET_ALIAS} #{name}") + PuppetX::Minio::Client.execute("admin policy remove #{@alias} #{name}") end def to_puppet_policy(json) diff --git a/lib/puppet/provider/minio_user/minio_user.rb b/lib/puppet/provider/minio_user/minio_user.rb index 8029cb9..851cf06 100644 --- a/lib/puppet/provider/minio_user/minio_user.rb +++ b/lib/puppet/provider/minio_user/minio_user.rb @@ -4,20 +4,22 @@ require 'puppet_x/minio/client' require 'puppet_x/minio/util' -DEFAUlT_TARGET_ALIAS ||= 'puppet'.freeze - # Implementation for the minio_user type using the Resource API. class Puppet::Provider::MinioUser::MinioUser < Puppet::ResourceApi::SimpleProvider include PuppetX::Minio::Util + def initialize + @alias = PuppetX::Minio::Client.alias + end + def get(context) context.debug('Returning list of minio users') - return [] unless PuppetX::Minio::Client.installed? + return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? @instances = [] - PuppetX::Minio::Client.execute("admin user list #{DEFAUlT_TARGET_ALIAS}").each do |json_user| + PuppetX::Minio::Client.execute("admin user list #{@alias}").each do |json_user| # `mcli admin user info` returns an array - json_user_info = PuppetX::Minio::Client.execute("admin user info #{DEFAUlT_TARGET_ALIAS} #{json_user['accessKey']}").first + json_user_info = PuppetX::Minio::Client.execute("admin user info #{@alias} #{json_user['accessKey']}").first @instances << to_puppet_user(json_user_info) end @instances @@ -27,8 +29,8 @@ def create(context, name, should) context.notice("Creating '#{name}' with #{should.inspect}") operations = [] - operations << ["admin user add #{DEFAUlT_TARGET_ALIAS} #{should[:access_key]} #{unwrap_maybe_sensitive(should[:secret_key])}", sensitive: true] - operations << ["admin policy set #{DEFAUlT_TARGET_ALIAS} #{should[:policies].join(' ')} user=#{name}"] unless should[:policies].nil? + operations << ["admin user add #{@alias} #{should[:access_key]} #{unwrap_maybe_sensitive(should[:secret_key])}", sensitive: true] + operations << ["admin policy set #{@alias} #{should[:policies].join(' ')} user=#{name}"] unless should[:policies].nil? operations.each do |op| PuppetX::Minio::Client.execute(*op) @@ -39,7 +41,7 @@ def update(context, name, should) context.notice("Updating '#{name}' with #{should.inspect}") operations = [] - operations << "admin policy set #{DEFAUlT_TARGET_ALIAS} #{should[:policies].join(' ')} user=#{name}" unless should[:policies].nil? + operations << "admin policy set #{@alias} #{should[:policies].join(' ')} user=#{name}" unless should[:policies].nil? operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -48,7 +50,7 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - PuppetX::Minio::Client.execute("admin user remove #{DEFAUlT_TARGET_ALIAS} #{name}") + PuppetX::Minio::Client.execute("admin user remove #{@alias} #{name}") end def insync?(context, _name, property_name, is_hash, should_hash) diff --git a/lib/puppet/type/minio_bucket.rb b/lib/puppet/type/minio_bucket.rb index 926e557..95d6ae7 100644 --- a/lib/puppet/type/minio_bucket.rb +++ b/lib/puppet/type/minio_bucket.rb @@ -13,7 +13,7 @@ **Autorequires**: * `File[/root/.minioclient]` -* `Minio_client_alias[puppet]` +* `File[/root/.minio_default_alias]` EOS features: [], attributes: { diff --git a/lib/puppet/type/minio_group.rb b/lib/puppet/type/minio_group.rb index fbe5b53..da117fd 100644 --- a/lib/puppet/type/minio_group.rb +++ b/lib/puppet/type/minio_group.rb @@ -21,7 +21,7 @@ **Autorequires**: * `File[/root/.minioclient]` -* `Minio_client_alias[puppet]` +* `File[/root/.minio_default_alias]` EOS features: [], attributes: { diff --git a/lib/puppet/type/minio_policy.rb b/lib/puppet/type/minio_policy.rb index db8c2aa..93aecab 100644 --- a/lib/puppet/type/minio_policy.rb +++ b/lib/puppet/type/minio_policy.rb @@ -25,7 +25,7 @@ **Autorequires**: * `File[/root/.minioclient]` -* `Minio_client_alias[puppet]` +* `File[/root/.minio_default_alias]` EOS features: ['canonicalize'], attributes: { diff --git a/lib/puppet/type/minio_user.rb b/lib/puppet/type/minio_user.rb index 09e40de..073fcf4 100644 --- a/lib/puppet/type/minio_user.rb +++ b/lib/puppet/type/minio_user.rb @@ -15,7 +15,7 @@ **Autorequires**: * `File[/root/.minioclient]` -* `Minio_client_alias[puppet]` +* `File[/root/.minio_default_alias]` EOS features: ['custom_insync'], attributes: { From 4694bab58b038a452e1449054e58b006f84b514f Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:15:50 +0200 Subject: [PATCH 09/19] Add minio_policy_assignment provider --- .../minio_policy_assignment.rb | 68 +++++++++++++++++++ lib/puppet/type/minio_policy_assignment.rb | 46 +++++++++++++ .../minio_policy_assignment_spec.rb | 52 ++++++++++++++ .../type/minio_policy_assignment_spec.rb | 10 +++ 4 files changed, 176 insertions(+) create mode 100644 lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb create mode 100644 lib/puppet/type/minio_policy_assignment.rb create mode 100644 spec/unit/puppet/provider/minio_policy_assignment/minio_policy_assignment_spec.rb create mode 100644 spec/unit/puppet/type/minio_policy_assignment_spec.rb diff --git a/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb b/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb new file mode 100644 index 0000000..87be770 --- /dev/null +++ b/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'puppet/resource_api/simple_provider' +require 'puppet_x/minio/client' + +# Implementation for the minio_policy_assignment type using the Resource API. +class Puppet::Provider::MinioPolicyAssignment::MinioPolicyAssignment < Puppet::ResourceApi::SimpleProvider + def initialize + @alias = PuppetX::Minio::Client.alias + end + + def get(context) + context.debug('Returning list of minio policy assignments') + return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? + + @instances = [] + + PuppetX::Minio::Client.execute("admin user list #{@alias}").each do |json_user| + # `mcli admin user info` returns an array + json_user_info = PuppetX::Minio::Client.execute("admin user info #{@alias} #{json_user['accessKey']}").first + @instances << to_puppet_policy_assignment(json_user_info, 'user') + end + + # `mcli admin group list` returns an array + json_groups = PuppetX::Minio::Client.execute("admin group list #{@alias}").first + json_groups.fetch('groups', []).each do |group| + # `mcli admin group info` returns an array + json_group_info = PuppetX::Minio::Client.execute("admin group info #{@alias} #{group}").first + @instances << to_puppet_policy_assignment(json_group_info, 'group') + end + + @instances + end + + def update(context, name, should) + context.notice("Updating '#{name}' with #{should.inspect}") + PuppetX::Minio::Client.execute("admin policy set #{@alias} #{should[:policies].join(',')} #{should[:subject_type]}=#{should[:subject]}") + end + + def create(context, name, should) + context.warning('`create` method not implemented for `minio_policy_assignment` provider.') + end + + def delete(context, name) + context.warning('`delete` method not implemented for `minio_policy_assignment` provider.') + end + + def to_puppet_policy_assignment(json, subject_type) + case subject_type + when 'user' + subject = json.fetch('accessKey') + policies = json.fetch('policyName', '').split(',') + when 'group' + subject = json.fetch('groupName') + policies = json.fetch('groupPolicy', '').split(',') + else + raise Puppet::ExecutionFailure, "Unknown subject_type `#{subject_type}`. Supported values: user, group" + end + + { + ensure: 'present', + title: "#{subject_type}_#{subject}", + subject: subject, + subject_type: subject_type, + policies: policies, + } + end +end diff --git a/lib/puppet/type/minio_policy_assignment.rb b/lib/puppet/type/minio_policy_assignment.rb new file mode 100644 index 0000000..706c481 --- /dev/null +++ b/lib/puppet/type/minio_policy_assignment.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'puppet/resource_api' + +Puppet::ResourceApi.register_type( + name: 'minio_policy_assignment', + docs: <<-EOS, +@summary Assigns MinIO policies to users or groups +@example +minio_policy_assignment { 'test': + ensure => 'present', +} + +**Autorequires**: +* `File[/root/.minioclient]` +* `File[/root/.minio_default_alias]` +EOS + features: [], + title_patterns: [ + { + pattern: %r{^(?.*)_(?.*)$}, + desc: 'Subject type and subject are both provided with a hyphen seperator', + }, + ], + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this resource should be present or absent on the target system.', + default: 'present', + }, + subject_type: { + type: 'Enum[user, group]', + desc: 'The type of subject to assign policie to. Should be user or group.', + behaviour: :namevar, + }, + subject: { + type: 'String', + desc: 'The user or group to assign policies to.', + behaviour: :namevar, + }, + policies: { + type: 'Array[String]', + desc: 'List of policies to assign to the subject.', + }, + }, +) diff --git a/spec/unit/puppet/provider/minio_policy_assignment/minio_policy_assignment_spec.rb b/spec/unit/puppet/provider/minio_policy_assignment/minio_policy_assignment_spec.rb new file mode 100644 index 0000000..2825a99 --- /dev/null +++ b/spec/unit/puppet/provider/minio_policy_assignment/minio_policy_assignment_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +ensure_module_defined('Puppet::Provider::MinioPolicyAssignment') +require 'puppet/provider/minio_policy_assignment/minio_policy_assignment' + +RSpec.describe Puppet::Provider::MinioPolicyAssignment::MinioPolicyAssignment do + subject(:provider) { described_class.new } + + let(:context) { instance_double('Puppet::ResourceApi::BaseContext', 'context') } + + describe '#get' do + it 'processes resources' do + expect(context).to receive(:debug).with('Returning pre-canned example data') + expect(provider.get(context)).to eq [ + { + name: 'foo', + ensure: 'present', + }, + { + name: 'bar', + ensure: 'present', + }, + ] + end + end + + describe 'create(context, name, should)' do + it 'creates the resource' do + expect(context).to receive(:notice).with(%r{\ACreating 'a'}) + + provider.create(context, 'a', name: 'a', ensure: 'present') + end + end + + describe 'update(context, name, should)' do + it 'updates the resource' do + expect(context).to receive(:notice).with(%r{\AUpdating 'foo'}) + + provider.update(context, 'foo', name: 'foo', ensure: 'present') + end + end + + describe 'delete(context, name)' do + it 'deletes the resource' do + expect(context).to receive(:notice).with(%r{\ADeleting 'foo'}) + + provider.delete(context, 'foo') + end + end +end diff --git a/spec/unit/puppet/type/minio_policy_assignment_spec.rb b/spec/unit/puppet/type/minio_policy_assignment_spec.rb new file mode 100644 index 0000000..a5ee0ec --- /dev/null +++ b/spec/unit/puppet/type/minio_policy_assignment_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'puppet/type/minio_policy_assignment' + +RSpec.describe 'the minio_policy_assignment type' do + it 'loads' do + expect(Puppet::Type.type(:minio_policy_assignment)).not_to be_nil + end +end From bce3f3e01b87effb0c6c4969dcca95c253ffc227 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 5 Apr 2022 17:06:09 +0200 Subject: [PATCH 10/19] lib/puppet/minio_user: Remove policy assignment This has been moved to a dedicated minio_policy_assignment provider. --- lib/puppet/provider/minio_user/minio_user.rb | 8 ++------ lib/puppet/type/minio_user.rb | 9 --------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/puppet/provider/minio_user/minio_user.rb b/lib/puppet/provider/minio_user/minio_user.rb index 851cf06..327dfa8 100644 --- a/lib/puppet/provider/minio_user/minio_user.rb +++ b/lib/puppet/provider/minio_user/minio_user.rb @@ -30,7 +30,7 @@ def create(context, name, should) operations = [] operations << ["admin user add #{@alias} #{should[:access_key]} #{unwrap_maybe_sensitive(should[:secret_key])}", sensitive: true] - operations << ["admin policy set #{@alias} #{should[:policies].join(' ')} user=#{name}"] unless should[:policies].nil? + operations << ["admin user disable #{@alias} #{should[:access_key]}"] unless should[:enabled] operations.each do |op| PuppetX::Minio::Client.execute(*op) @@ -41,7 +41,7 @@ def update(context, name, should) context.notice("Updating '#{name}' with #{should.inspect}") operations = [] - operations << "admin policy set #{@alias} #{should[:policies].join(' ')} user=#{name}" unless should[:policies].nil? + operations << "admin user disable #{@alias} #{name}" unless should[:enabled] operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -64,13 +64,9 @@ def insync?(context, _name, property_name, is_hash, should_hash) end def to_puppet_user(json) - policies = if json['policyName'].nil? then nil else json['policyName'].split(',') end - { ensure: 'present', access_key: json['accessKey'], - policies: policies, - member_of: json['memberOf'] || [], } end end diff --git a/lib/puppet/type/minio_user.rb b/lib/puppet/type/minio_user.rb index 073fcf4..6a9c833 100644 --- a/lib/puppet/type/minio_user.rb +++ b/lib/puppet/type/minio_user.rb @@ -33,14 +33,5 @@ type: 'Variant[Sensitive[String], String]', desc: 'The API access secret', }, - policies: { - type: 'Optional[Array[String]]', - desc: 'List of MinIO PBAC policies to set for this user.', - }, - member_of: { - type: 'Optional[Array[String]]', - desc: 'List of groups the user is a member of.', - behaviour: :read_only, - }, }, ) From 869cdd8cbe50c0a73c8cbe7875ffa786f8a7f702 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:18:43 +0200 Subject: [PATCH 11/19] lib/puppet/type/minio_group: Remove policy assignment Moved to minio_policy_assignment. --- lib/puppet/provider/minio_group/minio_group.rb | 3 --- lib/puppet/type/minio_group.rb | 6 ------ 2 files changed, 9 deletions(-) diff --git a/lib/puppet/provider/minio_group/minio_group.rb b/lib/puppet/provider/minio_group/minio_group.rb index 960105e..86e295e 100644 --- a/lib/puppet/provider/minio_group/minio_group.rb +++ b/lib/puppet/provider/minio_group/minio_group.rb @@ -36,9 +36,7 @@ def create(context, name, should) operations = [] operations << "admin group add #{@alias} #{name} #{should[:members].join(' ')}" - operations << "admin group disable #{@alias} #{name}" unless should[:enabled] - operations << "admin policy set #{@alias} #{should[:policies].join(',')} group=#{name}" unless should[:policies].nil? operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -74,7 +72,6 @@ def to_puppet_group(json) ensure: 'present', name: json['groupName'], members: json['members'] || [], - policies: policies, enabled: GROUP_STATUS_MAP[json['groupStatus'].to_sym], } end diff --git a/lib/puppet/type/minio_group.rb b/lib/puppet/type/minio_group.rb index da117fd..77e4bda 100644 --- a/lib/puppet/type/minio_group.rb +++ b/lib/puppet/type/minio_group.rb @@ -10,13 +10,11 @@ minio_group { 'admins': ensure => 'present', members => ['userOne', 'userTwo'], - policies => ['consoleAdmin'], } @example minio_group { 'my-group': ensure => 'present', members => ['userThree', 'userFour'], - policies => ['custom-policy'], } **Autorequires**: @@ -44,9 +42,5 @@ desc: 'Set to false to disable this group. Defaults to true.', default: true, }, - policies: { - type: 'Optional[Array[String]]', - desc: 'List of MinIO PBAC policies to set for this group.', - }, }, ) From 44d0f7eaa2d4ad790f7a0c5cff6dc5fb10c51443 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:57:04 +0200 Subject: [PATCH 12/19] lib/puppet/type/minio_user: Add secret key length restrictions --- lib/puppet/type/minio_user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/type/minio_user.rb b/lib/puppet/type/minio_user.rb index 6a9c833..a0d9c6e 100644 --- a/lib/puppet/type/minio_user.rb +++ b/lib/puppet/type/minio_user.rb @@ -30,7 +30,7 @@ behaviour: :namevar, }, secret_key: { - type: 'Variant[Sensitive[String], String]', + type: 'Variant[Sensitive[String[8, 40]], String[8, 40]]', desc: 'The API access secret', }, }, From 1b08458c539b2432ba50d4bc56a065d80bcb1f19 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 5 Apr 2022 16:55:11 +0200 Subject: [PATCH 13/19] lib/puppet/type/minio_user: Clarify parameter descriptions --- lib/puppet/type/minio_user.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/type/minio_user.rb b/lib/puppet/type/minio_user.rb index a0d9c6e..1f6c9cc 100644 --- a/lib/puppet/type/minio_user.rb +++ b/lib/puppet/type/minio_user.rb @@ -26,12 +26,12 @@ }, access_key: { type: 'String', - desc: 'The API access key', + desc: 'API access key. This can also be used as a username.', behaviour: :namevar, }, secret_key: { type: 'Variant[Sensitive[String[8, 40]], String[8, 40]]', - desc: 'The API access secret', + desc: 'API access secret or password.', }, }, ) From 0fdb959b0f73c4866978165bd34d633e3eca50a4 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Thu, 7 Apr 2022 11:39:58 +0200 Subject: [PATCH 14/19] lib/puppet/minio_user: Add enabled param --- lib/puppet/provider/minio_user/minio_user.rb | 6 ++++++ lib/puppet/type/minio_user.rb | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/lib/puppet/provider/minio_user/minio_user.rb b/lib/puppet/provider/minio_user/minio_user.rb index 327dfa8..9b593fe 100644 --- a/lib/puppet/provider/minio_user/minio_user.rb +++ b/lib/puppet/provider/minio_user/minio_user.rb @@ -4,6 +4,11 @@ require 'puppet_x/minio/client' require 'puppet_x/minio/util' +STATUS_MAP ||= { + 'enabled': true, + 'disabled': false +}.freeze + # Implementation for the minio_user type using the Resource API. class Puppet::Provider::MinioUser::MinioUser < Puppet::ResourceApi::SimpleProvider include PuppetX::Minio::Util @@ -67,6 +72,7 @@ def to_puppet_user(json) { ensure: 'present', access_key: json['accessKey'], + enabled: STATUS_MAP[json['userStatus'].to_sym], } end end diff --git a/lib/puppet/type/minio_user.rb b/lib/puppet/type/minio_user.rb index 1f6c9cc..245ed1f 100644 --- a/lib/puppet/type/minio_user.rb +++ b/lib/puppet/type/minio_user.rb @@ -33,5 +33,10 @@ type: 'Variant[Sensitive[String[8, 40]], String[8, 40]]', desc: 'API access secret or password.', }, + enabled: { + type: 'Optional[Boolean]', + desc: 'Enables/Disables this user account', + default: true, + }, }, ) From 0311ba8f559e3f380bb7f8c01dc92e80bea6c504 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Thu, 7 Apr 2022 10:21:33 +0200 Subject: [PATCH 15/19] lib/puppet/provider/minio_policy: Also sanitize non-symbolizes keys --- lib/puppet/provider/minio_policy/minio_policy.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/minio_policy/minio_policy.rb b/lib/puppet/provider/minio_policy/minio_policy.rb index ea0e6ac..2dc1a97 100644 --- a/lib/puppet/provider/minio_policy/minio_policy.rb +++ b/lib/puppet/provider/minio_policy/minio_policy.rb @@ -35,7 +35,7 @@ def create(context, name, should) f = Tempfile.new(["#{name}-policy", '.json']) begin - json_policy = Hash[:Version => DEFAULT_POLICY_VERSION, :Statement => should[:statement]].to_json + json_policy = {:Version => DEFAULT_POLICY_VERSION, :Statement => should[:statement]}.to_json f.write(json_policy) f.rewind @@ -78,7 +78,7 @@ def to_puppet_policy(json) def sanitize_statement(statement) statement.transform_keys!(&:capitalize) - [:Action, :Resource].each do |k| + ['Action', 'Resource', :Action, :Resource].each do |k| statement[k].sort! unless statement[k].nil? end From a79bedc61cc046c3a512286b4697b2ac43eef25b Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:09:49 +0200 Subject: [PATCH 16/19] lib/puppet/minio_policy: Use default for version parameter --- lib/puppet/provider/minio_policy/minio_policy.rb | 4 +--- lib/puppet/type/minio_policy.rb | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/puppet/provider/minio_policy/minio_policy.rb b/lib/puppet/provider/minio_policy/minio_policy.rb index 2dc1a97..d232add 100644 --- a/lib/puppet/provider/minio_policy/minio_policy.rb +++ b/lib/puppet/provider/minio_policy/minio_policy.rb @@ -6,8 +6,6 @@ require 'puppet/resource_api/simple_provider' require 'puppet_x/minio/client' -DEFAULT_POLICY_VERSION ||= '2012-10-17'.freeze - # Implementation for the minio_policy type using the Resource API. class Puppet::Provider::MinioPolicy::MinioPolicy < Puppet::ResourceApi::SimpleProvider def initialize @@ -35,7 +33,7 @@ def create(context, name, should) f = Tempfile.new(["#{name}-policy", '.json']) begin - json_policy = {:Version => DEFAULT_POLICY_VERSION, :Statement => should[:statement]}.to_json + json_policy = {:Version => should[:version], :Statement => should[:statement]}.to_json f.write(json_policy) f.rewind diff --git a/lib/puppet/type/minio_policy.rb b/lib/puppet/type/minio_policy.rb index 93aecab..b25cc30 100644 --- a/lib/puppet/type/minio_policy.rb +++ b/lib/puppet/type/minio_policy.rb @@ -40,9 +40,9 @@ behaviour: :namevar, }, version: { - type: 'String', - desc: 'Specifies the language syntax rules that are to be used to process a policy.', - behaviour: :read_only, + type: 'Optional[String]', + desc: 'Specifies the language syntax rules that are to be used to process a policy.', + default: '2012-10-17', }, statement: { type: 'Array[Hash]', From 21526e89f766195bc25fccfb7cf83095c5bacd12 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Wed, 6 Apr 2022 16:46:11 +0200 Subject: [PATCH 17/19] lib/puppet/minio_bucket: Add region and object lock params --- .../provider/minio_bucket/minio_bucket.rb | 29 +++++++++++++++---- lib/puppet/type/minio_bucket.rb | 11 +++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/puppet/provider/minio_bucket/minio_bucket.rb b/lib/puppet/provider/minio_bucket/minio_bucket.rb index fcff22c..bedf04d 100644 --- a/lib/puppet/provider/minio_bucket/minio_bucket.rb +++ b/lib/puppet/provider/minio_bucket/minio_bucket.rb @@ -15,18 +15,34 @@ def get(context) @instances = [] PuppetX::Minio::Client.execute("ls #{@alias}").each do |json_bucket| - @instances << to_puppet_bucket(json_bucket) + name = json_bucket['key'].chomp('/') + # `mcli stat` returns an array + data = PuppetX::Minio::Client.execute("stat #{@alias}/#{name}").first + + @instances << to_puppet_bucket(data) end @instances end def create(context, name, should) context.notice("Creating '#{name}' with #{should.inspect}") - PuppetX::Minio::Client.execute("mb #{@alias}/#{name}") + + flags = [] + flags << "--region=#{should[:region]}" if should[:region] + flags << '--with-lock' if should[:enable_object_lock] + + PuppetX::Minio::Client.execute("mb #{flags.join(' ')} #{@alias}/#{name}") end def update(context, name, should) - context.warning('`update` method not implemented for `minio_bucket` provider') + context.notice("Updating '#{name}' with #{should.inspect}") + + operations = [] + operations << "retention clear #{@alias}/#{name}" unless should[:enable_object_lock] + + operations.each do |op| + PuppetX::Minio::Client.execute(op) + end end def delete(context, name) @@ -35,12 +51,15 @@ def delete(context, name) end def to_puppet_bucket(json) - # Delete trailing slashes from bucket name - name = json['key'].chomp('/') + name = json['url'].delete_prefix("#{@alias}/") + region = json['metadata']['location'] + enable_object_lock = ! json['metadata']['ObjectLock']['enabled'].empty? { ensure: 'present', name: name, + region: region, + enable_object_lock: enable_object_lock, } end end diff --git a/lib/puppet/type/minio_bucket.rb b/lib/puppet/type/minio_bucket.rb index 95d6ae7..6cb8974 100644 --- a/lib/puppet/type/minio_bucket.rb +++ b/lib/puppet/type/minio_bucket.rb @@ -27,5 +27,16 @@ desc: 'The name of the resource you want to manage.', behaviour: :namevar, }, + region: { + type: 'Optional[String]', + desc: 'Region where to create the bucket.', + behaviour: :init_only, + default: 'us-east-1', + }, + enable_object_lock: { + type: 'Optional[Boolean]', + desc: 'Enables/Disables S3 object locking.', + default: false, + } }, ) From 3bb883015d80d6f475c0017bec5aea5b13feee28 Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Thu, 7 Apr 2022 09:41:43 +0200 Subject: [PATCH 18/19] lib/puppet/minio_bucket: Add insync? method --- lib/puppet/provider/minio_bucket/minio_bucket.rb | 10 ++++++++++ lib/puppet/type/minio_bucket.rb | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/puppet/provider/minio_bucket/minio_bucket.rb b/lib/puppet/provider/minio_bucket/minio_bucket.rb index bedf04d..a3e5e2f 100644 --- a/lib/puppet/provider/minio_bucket/minio_bucket.rb +++ b/lib/puppet/provider/minio_bucket/minio_bucket.rb @@ -62,4 +62,14 @@ def to_puppet_bucket(json) enable_object_lock: enable_object_lock, } end + + def insync?(context, _name, property_name, is_hash, should_hash) + context.debug("Checking whether #{property_name} is out of sync") + case property_name + when :region + # It's not possible to move a bucket to a different region + # after creation. + true + end + end end diff --git a/lib/puppet/type/minio_bucket.rb b/lib/puppet/type/minio_bucket.rb index 6cb8974..1f19387 100644 --- a/lib/puppet/type/minio_bucket.rb +++ b/lib/puppet/type/minio_bucket.rb @@ -15,7 +15,7 @@ * `File[/root/.minioclient]` * `File[/root/.minio_default_alias]` EOS - features: [], + features: ['custom_insync'], attributes: { ensure: { type: 'Enum[present, absent]', From bf9a2adeb3651c33955820a603fd23718dc8aa7b Mon Sep 17 00:00:00 2001 From: jonasdemoor <17252323+jonasdemoor@users.noreply.github.com> Date: Tue, 12 Apr 2022 15:58:12 +0200 Subject: [PATCH 19/19] providers: Use client alias method directly for retrieving alias Instead of setting a global variable in the constructor. Otherwise, catalog compilation fails at the first resource. --- .../provider/minio_bucket/minio_bucket.rb | 16 ++++++---------- lib/puppet/provider/minio_group/minio_group.rb | 18 +++++++----------- .../provider/minio_policy/minio_policy.rb | 12 ++++-------- .../minio_policy_assignment.rb | 15 +++++---------- lib/puppet/provider/minio_user/minio_user.rb | 16 ++++++---------- 5 files changed, 28 insertions(+), 49 deletions(-) diff --git a/lib/puppet/provider/minio_bucket/minio_bucket.rb b/lib/puppet/provider/minio_bucket/minio_bucket.rb index a3e5e2f..fc47124 100644 --- a/lib/puppet/provider/minio_bucket/minio_bucket.rb +++ b/lib/puppet/provider/minio_bucket/minio_bucket.rb @@ -5,19 +5,15 @@ # Implementation for the minio_bucket type using the Resource API. class Puppet::Provider::MinioBucket::MinioBucket < Puppet::ResourceApi::SimpleProvider - def initialize - @alias = PuppetX::Minio::Client.alias - end - def get(context) context.debug('Returning list of minio buckets') return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? @instances = [] - PuppetX::Minio::Client.execute("ls #{@alias}").each do |json_bucket| + PuppetX::Minio::Client.execute("ls #{PuppetX::Minio::Client.alias}").each do |json_bucket| name = json_bucket['key'].chomp('/') # `mcli stat` returns an array - data = PuppetX::Minio::Client.execute("stat #{@alias}/#{name}").first + data = PuppetX::Minio::Client.execute("stat #{PuppetX::Minio::Client.alias}/#{name}").first @instances << to_puppet_bucket(data) end @@ -31,14 +27,14 @@ def create(context, name, should) flags << "--region=#{should[:region]}" if should[:region] flags << '--with-lock' if should[:enable_object_lock] - PuppetX::Minio::Client.execute("mb #{flags.join(' ')} #{@alias}/#{name}") + PuppetX::Minio::Client.execute("mb #{flags.join(' ')} #{PuppetX::Minio::Client.alias}/#{name}") end def update(context, name, should) context.notice("Updating '#{name}' with #{should.inspect}") operations = [] - operations << "retention clear #{@alias}/#{name}" unless should[:enable_object_lock] + operations << "retention clear #{PuppetX::Minio::Client.alias}/#{name}" unless should[:enable_object_lock] operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -47,11 +43,11 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - PuppetX::Minio::Client.execute("rb --force #{@alias}/#{name}") + PuppetX::Minio::Client.execute("rb --force #{PuppetX::Minio::Client.alias}/#{name}") end def to_puppet_bucket(json) - name = json['url'].delete_prefix("#{@alias}/") + name = json['url'].delete_prefix("#{PuppetX::Minio::Client.alias}/") region = json['metadata']['location'] enable_object_lock = ! json['metadata']['ObjectLock']['enabled'].empty? diff --git a/lib/puppet/provider/minio_group/minio_group.rb b/lib/puppet/provider/minio_group/minio_group.rb index 86e295e..eb7f47e 100644 --- a/lib/puppet/provider/minio_group/minio_group.rb +++ b/lib/puppet/provider/minio_group/minio_group.rb @@ -10,22 +10,18 @@ # Implementation for the minio_group type using the Resource API. class Puppet::Provider::MinioGroup::MinioGroup < Puppet::ResourceApi::SimpleProvider - def initialize - @alias = PuppetX::Minio::Client.alias - end - def get(context) context.debug('Returning list of minio groups') return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? # `mcli admin group list` returns an array - json_groups = PuppetX::Minio::Client.execute("admin group list #{@alias}").first + json_groups = PuppetX::Minio::Client.execute("admin group list #{PuppetX::Minio::Client.alias}").first return [] unless json_groups.key?('groups') @instances = [] json_groups['groups'].each do |group| # `mcli admin group info` returns an array - json_group_info = PuppetX::Minio::Client.execute("admin group info #{@alias} #{group}").first + json_group_info = PuppetX::Minio::Client.execute("admin group info #{PuppetX::Minio::Client.alias} #{group}").first @instances << to_puppet_group(json_group_info) end @instances @@ -35,8 +31,8 @@ def create(context, name, should) context.notice("Creating '#{name}' with #{should.inspect}") operations = [] - operations << "admin group add #{@alias} #{name} #{should[:members].join(' ')}" - operations << "admin group disable #{@alias} #{name}" unless should[:enabled] + operations << "admin group add #{PuppetX::Minio::Client.alias} #{name} #{should[:members].join(' ')}" + operations << "admin group disable #{PuppetX::Minio::Client.alias} #{name}" unless should[:enabled] operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -54,11 +50,11 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - members = PuppetX::Minio::Client.execute("admin group info #{@alias} #{name}").first['members'] + members = PuppetX::Minio::Client.execute("admin group info #{PuppetX::Minio::Client.alias} #{name}").first['members'] operations = [] - operations << "admin group remove #{@alias} #{name} #{members.join(' ')}" - operations << "admin group remove #{@alias} #{name}" + operations << "admin group remove #{PuppetX::Minio::Client.alias} #{name} #{members.join(' ')}" + operations << "admin group remove #{PuppetX::Minio::Client.alias} #{name}" operations.each do |op| PuppetX::Minio::Client.execute(op) diff --git a/lib/puppet/provider/minio_policy/minio_policy.rb b/lib/puppet/provider/minio_policy/minio_policy.rb index d232add..6677380 100644 --- a/lib/puppet/provider/minio_policy/minio_policy.rb +++ b/lib/puppet/provider/minio_policy/minio_policy.rb @@ -8,21 +8,17 @@ # Implementation for the minio_policy type using the Resource API. class Puppet::Provider::MinioPolicy::MinioPolicy < Puppet::ResourceApi::SimpleProvider - def initialize - @alias = PuppetX::Minio::Client.alias - end - def get(context) context.debug('Returning list of minio policies') return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? - json_policies = PuppetX::Minio::Client.execute("admin policy list #{@alias}") + json_policies = PuppetX::Minio::Client.execute("admin policy list #{PuppetX::Minio::Client.alias}") return [] if json_policies.empty? @instances = [] json_policies.each do |policy| # `mcli admin policy info` returns an array - json_policy_info = PuppetX::Minio::Client.execute("admin policy info #{@alias} #{policy['policy']}").first + json_policy_info = PuppetX::Minio::Client.execute("admin policy info #{PuppetX::Minio::Client.alias} #{policy['policy']}").first @instances << to_puppet_policy(json_policy_info) end @instances @@ -38,7 +34,7 @@ def create(context, name, should) f.write(json_policy) f.rewind - PuppetX::Minio::Client.execute("admin policy add #{@alias} #{name} #{f.path}") + PuppetX::Minio::Client.execute("admin policy add #{PuppetX::Minio::Client.alias} #{name} #{f.path}") ensure f.close f.unlink @@ -56,7 +52,7 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - PuppetX::Minio::Client.execute("admin policy remove #{@alias} #{name}") + PuppetX::Minio::Client.execute("admin policy remove #{PuppetX::Minio::Client.alias} #{name}") end def to_puppet_policy(json) diff --git a/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb b/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb index 87be770..44d4308 100644 --- a/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb +++ b/lib/puppet/provider/minio_policy_assignment/minio_policy_assignment.rb @@ -5,27 +5,22 @@ # Implementation for the minio_policy_assignment type using the Resource API. class Puppet::Provider::MinioPolicyAssignment::MinioPolicyAssignment < Puppet::ResourceApi::SimpleProvider - def initialize - @alias = PuppetX::Minio::Client.alias - end - def get(context) context.debug('Returning list of minio policy assignments') return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? @instances = [] - - PuppetX::Minio::Client.execute("admin user list #{@alias}").each do |json_user| + PuppetX::Minio::Client.execute("admin user list #{PuppetX::Minio::Client.alias}").each do |json_user| # `mcli admin user info` returns an array - json_user_info = PuppetX::Minio::Client.execute("admin user info #{@alias} #{json_user['accessKey']}").first + json_user_info = PuppetX::Minio::Client.execute("admin user info #{PuppetX::Minio::Client.alias} #{json_user['accessKey']}").first @instances << to_puppet_policy_assignment(json_user_info, 'user') end # `mcli admin group list` returns an array - json_groups = PuppetX::Minio::Client.execute("admin group list #{@alias}").first + json_groups = PuppetX::Minio::Client.execute("admin group list #{PuppetX::Minio::Client.alias}").first json_groups.fetch('groups', []).each do |group| # `mcli admin group info` returns an array - json_group_info = PuppetX::Minio::Client.execute("admin group info #{@alias} #{group}").first + json_group_info = PuppetX::Minio::Client.execute("admin group info #{PuppetX::Minio::Client.alias} #{group}").first @instances << to_puppet_policy_assignment(json_group_info, 'group') end @@ -34,7 +29,7 @@ def get(context) def update(context, name, should) context.notice("Updating '#{name}' with #{should.inspect}") - PuppetX::Minio::Client.execute("admin policy set #{@alias} #{should[:policies].join(',')} #{should[:subject_type]}=#{should[:subject]}") + PuppetX::Minio::Client.execute("admin policy set #{PuppetX::Minio::Client.alias} #{should[:policies].join(',')} #{should[:subject_type]}=#{should[:subject]}") end def create(context, name, should) diff --git a/lib/puppet/provider/minio_user/minio_user.rb b/lib/puppet/provider/minio_user/minio_user.rb index 9b593fe..050362f 100644 --- a/lib/puppet/provider/minio_user/minio_user.rb +++ b/lib/puppet/provider/minio_user/minio_user.rb @@ -13,18 +13,14 @@ class Puppet::Provider::MinioUser::MinioUser < Puppet::ResourceApi::SimpleProvider include PuppetX::Minio::Util - def initialize - @alias = PuppetX::Minio::Client.alias - end - def get(context) context.debug('Returning list of minio users') return [] unless PuppetX::Minio::Client.installed? || PuppetX::Minio::Client.alias_set? @instances = [] - PuppetX::Minio::Client.execute("admin user list #{@alias}").each do |json_user| + PuppetX::Minio::Client.execute("admin user list #{PuppetX::Minio::Client.alias}").each do |json_user| # `mcli admin user info` returns an array - json_user_info = PuppetX::Minio::Client.execute("admin user info #{@alias} #{json_user['accessKey']}").first + json_user_info = PuppetX::Minio::Client.execute("admin user info #{PuppetX::Minio::Client.alias} #{json_user['accessKey']}").first @instances << to_puppet_user(json_user_info) end @instances @@ -34,8 +30,8 @@ def create(context, name, should) context.notice("Creating '#{name}' with #{should.inspect}") operations = [] - operations << ["admin user add #{@alias} #{should[:access_key]} #{unwrap_maybe_sensitive(should[:secret_key])}", sensitive: true] - operations << ["admin user disable #{@alias} #{should[:access_key]}"] unless should[:enabled] + operations << ["admin user add #{PuppetX::Minio::Client.alias} #{should[:access_key]} #{unwrap_maybe_sensitive(should[:secret_key])}", sensitive: true] + operations << ["admin user disable #{PuppetX::Minio::Client.alias} #{should[:access_key]}"] unless should[:enabled] operations.each do |op| PuppetX::Minio::Client.execute(*op) @@ -46,7 +42,7 @@ def update(context, name, should) context.notice("Updating '#{name}' with #{should.inspect}") operations = [] - operations << "admin user disable #{@alias} #{name}" unless should[:enabled] + operations << "admin user disable #{PuppetX::Minio::Client.alias} #{name}" unless should[:enabled] operations.each do |op| PuppetX::Minio::Client.execute(op) @@ -55,7 +51,7 @@ def update(context, name, should) def delete(context, name) context.notice("Deleting '#{name}'") - PuppetX::Minio::Client.execute("admin user remove #{@alias} #{name}") + PuppetX::Minio::Client.execute("admin user remove #{PuppetX::Minio::Client.alias} #{name}") end def insync?(context, _name, property_name, is_hash, should_hash)