All Files (86.6% covered at 15.03 hits/line)
52 files in total.
1239 relevant lines.
1073 lines covered and
166 lines missed
# frozen_string_literal: true
- 3
module AMA
- 3
module Chef
- 3
module User
# Action represents single action required to be taken to converge
# system into target state. Actions are created during planning phase
# and then run by feeding in current resource they are called from within
- 3
class Action
- 3
attr_accessor :class_name
# rubocop:disable Lint/UnusedMethodArgument
- 3
def apply(resource)
raise 'Abstract method left behind'
end
# rubocop:enable Lint/UnusedMethodArgument
- 3
protected
- 3
def noop
::Chef::Log.debug('Noop action')
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
class Create < Action
- 3
attr_accessor :account
# @param [AMA::Chef::User::Model::Account] account
- 3
def initialize(account)
- 16
@account = account
- 16
super()
end
- 3
def apply(resource_factory)
resource_factory.user @account.id.to_s
end
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Metrics/AbcSize
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PrivateKey
- 3
class Add < Action
- 3
attr_accessor :account
- 3
attr_accessor :key
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] key
- 3
def initialize(account, key)
- 3
@account = account
- 3
@key = key
end
- 3
def apply(resource_factory)
account = @account
key = @key
name = "#{account.id}:#{key.owner}:#{key.id}"
resource_factory.ssh_private_key name do
id key.id.to_s
user account.id.to_s
content key.content.to_s
install_public_key key.install_public_key
passphrase key.passphrase unless key.passphrase.nil?
perform_validation key.validate
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PrivateKey
- 3
class Purge < Action
- 3
attr_accessor :account
# @param [AMA::Chef::User::Model::Account] account
- 3
def initialize(account)
- 27
@account = account
end
- 3
def apply(*)
noop
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../../action'
- 3
require_relative '../../../../helper/ssh_methods'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PrivateKey
- 3
module Remote
- 3
class Add < Action
- 3
include Helper::SSHMethods
- 3
attr_accessor :account
- 3
attr_accessor :private_key
- 3
attr_accessor :remote
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] private_key
# @param [AMA::Chef::User::Model::PrivateKey::Remote] remote
- 3
def initialize(account, private_key, remote)
- 2
@account = account
- 2
@private_key = private_key
- 2
@remote = remote
end
- 3
def apply(resource_factory)
path = "#{ssh_directory(@account.id)}/#{@private_key.id}"
options = @remote.options.merge('IdentityFile' => path)
account = @account
key = @private_key
remote = @remote
name = "#{account.id}:#{key.owner}:#{key.id}:#{remote.id}"
resource_factory.ssh_config name do
user account.id.to_s
host remote.id.to_s
options options
end
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PrivateKey
- 3
module Remote
- 3
class Purge < Action
- 3
attr_accessor :account
- 3
attr_accessor :private_key
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] private_key
- 3
def initialize(account, private_key)
- 6
@account = account
- 6
@private_key = private_key
end
- 3
def apply(*)
noop
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PrivateKey
- 3
module Remote
- 3
class Remove < Action
- 3
attr_accessor :account
- 3
attr_accessor :private_key
- 3
attr_accessor :remote
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] private_key
# @param [AMA::Chef::User::Model::PrivateKey::Remote] remote
- 3
def initialize(account, private_key, remote)
- 1
@account = account
- 1
@private_key = private_key
- 1
@remote = remote
end
- 3
def apply(resource_factory)
account = @account
key = @private_key
remote = @remote
name = "#{account.id}:#{key.owner}:#{key.id}:#{remote.id}"
resource_factory.ssh_config name do
host remote.id.to_s
user account.id.to_s
action :remove
end
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PrivateKey
- 3
class Remove < Action
- 3
attr_accessor :account
- 3
attr_accessor :key
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] key
- 3
def initialize(account, key)
- 2
@account = account
- 2
@key = key
end
- 3
def apply(resource_factory)
account = @account
key = @key
name = "#{account.id}:#{key.owner}:#{key.id}"
content = (key.content || '').to_s
resource_factory.ssh_private_key name do
id key.id.to_s
user account.id.to_s
# TODO: remove when https://github.com/ama-team/cookbook-ssh-private-keys/issues/2
# is resolved
content content
action :remove
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
require_relative '../../..//handler/privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module Privilege
- 3
class Grant < Action
- 3
attr_accessor :account
- 3
attr_accessor :privilege
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::Privilege] privilege
- 3
def initialize(account, privilege)
- 4
@account = account
- 4
@privilege = privilege
end
- 3
def apply(resource_factory)
registry = ::AMA::Chef::User::Handler::Privilege
handler = registry.retrieve!(:account, @privilege.type)
handler.grant(resource_factory, @account, @privilege)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module Privilege
- 3
class Purge < Action
- 3
attr_accessor :account
# @param [AMA::Chef::User::Model::Account] account
- 3
def initialize(account)
- 26
@account = account
end
- 3
def apply(*)
noop
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module Privilege
- 3
class Revoke < Action
- 3
attr_accessor :account
- 3
attr_accessor :privilege
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::Privilege] type
- 3
def initialize(account, privilege)
- 3
@account = account
- 3
@privilege = privilege
end
- 3
def apply(resource_factory)
registry = ::AMA::Chef::User::Handler::Privilege
handler = registry.retrieve!(:account, @privilege.type)
handler.revoke(resource_factory, @account, @privilege)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PublicKey
- 3
class Add < Action
- 3
attr_accessor :account
- 3
attr_accessor :key
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PublicKey] public_key
- 3
def initialize(account, public_key)
- 16
@account = account
- 16
@key = public_key
end
- 3
def apply(resource_factory)
account = @account
key = @key
id = "#{account.id}:#{key.owner}:#{key.id}"
resource_factory.ssh_authorize_key id do
user account.id.to_s
key key.content
keytype key.type.to_s
comment "#{key.owner}:#{key.id}"
validate_key key.validate
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
require_relative '../../../helper/ssh_methods'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PublicKey
- 3
class Purge < Action
- 3
include Helper::SSHMethods
- 3
attr_accessor :account
# @param [AMA::Chef::User::Model::Account] account
- 3
def initialize(account)
- 18
@account = account
end
- 3
def apply(resource_factory)
resource_id = "#{ssh_directory(@account.id)}/authorized_keys"
::Chef::Log.debug("Purging ssh keys for account #{@account.id}")
resource_factory.file resource_id do
action :delete
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
module PublicKey
- 3
class Remove < Action
- 3
attr_accessor :account
- 3
attr_accessor :key
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PublicKey] key
- 3
def initialize(account, key)
- 7
@account = account
- 7
@key = key
end
- 3
def apply(*)
noop
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Account
- 3
class Remove < Action
- 3
attr_accessor :account
# @param [AMA::Chef::User::Model::Account] account
- 3
def initialize(account)
- 10
@account = account
end
- 3
def apply(resource_factory)
resource_factory.user @account.id.to_s do
action :remove
end
end
- 3
def to_s
"account[#{account.id}]:remove"
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
class AppendMembers < Action
- 3
attr_accessor :group
# @param [AMA::Chef::User::Model::Group] group
- 3
def initialize(group)
- 5
@group = group
end
- 3
def apply(resource_factory)
group = @group
resource_factory.group "#{group.id}:members:append" do
group_name group.id.to_s
members group.members.to_a
append true
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
class ExcludeMembers < Action
- 3
attr_accessor :group
- 3
attr_accessor :members
# @param [AMA::Chef::User::Model::Group] group
# @param [Enumerable] members
- 3
def initialize(group, members)
- 4
@group = group
- 4
@members = members
end
- 3
def apply(resource_factory)
members = @members
group = @group
resource_factory.group "#{group.id}:members:remove" do
group_name group.id.to_s
excluded_members members.to_a
append true
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
require_relative '../../../handler/privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
module Privilege
- 3
class Grant < Action
- 3
attr_accessor :group
- 3
attr_accessor :privilege
# @param [AMA::Chef::User::Model::Group] group
# @param [AMA::Chef::User::Model::Privilege] privilege
- 3
def initialize(group, privilege)
- 4
@group = group
- 4
@privilege = privilege
end
- 3
def apply(resource_factory)
registry = ::AMA::Chef::User::Handler::Privilege
handler = registry.retrieve!(:group, @privilege.type)
handler.grant(resource_factory, @group, @privilege)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
module Privilege
- 3
class Purge < Action
- 3
attr_accessor :group
# @param [AMA::Chef::User::Model::Group] group
- 3
def initialize(group)
- 15
@group = group
end
- 3
def apply(*)
noop
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../../action'
- 3
require_relative '../../../handler/privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
module Privilege
- 3
class Revoke < Action
- 3
attr_accessor :group
- 3
attr_accessor :privilege
# @param [AMA::Chef::User::Model::Group] group
# @param [AMA::Chef::User::Model::Privilege] privilege
- 3
def initialize(group, privilege)
- 3
@group = group
- 3
@privilege = privilege
end
- 3
def apply(resource_factory)
registry = ::AMA::Chef::User::Handler::Privilege
handler = registry.retrieve!(:group, @privilege.type)
handler.revoke(resource_factory, @group, @privilege)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
class Remove < Action
- 3
attr_accessor :group
# @param [AMA::Chef::User::Model::Group] group
- 3
def initialize(group)
- 5
@group = group
end
- 3
def apply(resource_factory)
group = @group
resource_factory.group "#{@group.id}:remove" do
group_name group.id.to_s
action :remove
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../action'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Action
- 3
module Group
- 3
class SetMembers < Action
- 3
attr_accessor :group
# @param [AMA::Chef::User::Model::Group] group
- 3
def initialize(group)
- 5
@group = group
end
- 3
def apply(resource_factory)
group = @group
resource_factory.group "#{group.id}:members:set" do
group_name group.id.to_s
members group.members.to_a
append false
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Exception
# Designed to be thrown whenever invalid key is supplied
- 3
class InvalidKeyException < ArgumentError
end
end
end
end
end
# frozen_string_literal: true
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Exception
# Thrown in case privilege handler isn't found
- 3
class MissingHandlerException < ArgumentError
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Lint/UnusedMethodArgument
- 3
require_relative '../exception/missing_handler_exception'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Handler
# Privilege handler base / registry
- 3
class Privilege
- 3
def grant(resource_factory, owner, privilege)
abstract_method_protector
end
- 3
def revoke(resource_factory, owner, privilege)
abstract_method_protector
end
- 3
private
- 3
def abstract_method_protector
raise 'Abstract method hasn\'t been implemented'
end
- 3
class << self
- 3
def register(domain, type, handler)
- 6
domain = domain.to_sym
- 6
type = type.to_sym
- 6
registry[domain] = {} unless registry[domain]
- 6
registry[domain][type] = handler
end
- 3
def retrieve(domain, type)
domain = domain.to_sym
type = type.to_sym
registry[domain] && registry[domain][type]
end
- 3
def retrieve!(domain, type)
handler = retrieve(domain, type)
return handler if handler
message = "No privilege handler with type #{type} " \
"has been found in domain #{domain}"
raise Exception::MissingHandlerException, message
end
- 3
private
- 3
def registry
- 18
@registry = {} unless @registry
- 18
@registry
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Handler
- 3
class Privilege
- 3
module Account
# Sudo privilege granter/revoker
- 3
class Sudo < Privilege
- 3
def grant(resource_factory, owner, privilege)
resource(resource_factory, owner, privilege).action = :install
end
- 3
def revoke(resource_factory, owner, privilege)
resource(resource_factory, owner, privilege).action = :remove
end
- 3
private
- 3
def resource(resource_factory, owner, privilege)
resource_factory.sudo "user:#{owner.id}" do
privilege.options.each do |key, value|
if self.class.properties.keys.include?(key)
send(key, value)
else
::Chef::Log.warn(
"Account #{owner.id} sudo privilege " \
"has unknown option #{key}"
)
end
end
user owner.id.to_s
end
end
# yes, i'm evading excessive line length
- 3
::AMA::Chef::User::Handler::Privilege.tap do |registry|
- 3
registry.register(:account, :sudo, new)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Handler
- 3
class Privilege
- 3
module Group
# Sudo privilege granter/revoker
- 3
class Sudo < Privilege
- 3
def grant(resource_factory, owner, privilege)
resource(resource_factory, owner, privilege).action = :install
end
- 3
def revoke(resource_factory, owner, privilege)
resource(resource_factory, owner, privilege).action = :remove
end
- 3
private
- 3
def resource(resource_factory, owner, privilege)
resource_factory.sudo "group:#{owner.id}" do
privilege.options.each do |key, value|
if self.class.properties.keys.include?(key)
send(key, value)
else
::Chef::Log.warn(
"Account #{owner.id} sudo privilege " \
"has unknown option #{key}"
)
end
end
group owner.id.to_s
end
end
# yes, i'm evading excessive line length
- 3
::AMA::Chef::User::Handler::Privilege.tap do |registry|
- 3
registry.register(:group, :sudo, new)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Helper
# Handful of methods that may be helpful for SSH file-related work
- 3
module SSHMethods
- 3
def home_directory(account_id)
Etc.getpwnam(account_id.to_s).dir
rescue ArgumentError
"/home/#{account_id}"
end
- 3
def ssh_directory(account_id)
"#{home_directory(account_id)}/.ssh"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'ama-entity-mapper'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Mixin
# Base methods for entities (models) used across project
- 3
module Entity
- 3
class << self
- 3
def included(klass)
- 39
klass.send(:include, AMA::Entity::Mapper::DSL)
end
end
- 3
def ==(other)
- 635
eql?(other)
end
- 3
def eql?(other)
- 635
return false unless other.is_a?(self.class) || is_a?(other.class)
- 272
return false unless other.instance_variables == instance_variables
- 272
instance_variables.each do |variable|
- 728
value = other.instance_variable_get(variable)
- 728
return false unless instance_variable_get(variable) == value
end
- 147
true
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../mixin/entity'
- 3
require_relative 'policy'
- 3
require_relative 'public_key'
- 3
require_relative 'private_key'
- 3
require_relative 'privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
# Depicts linux user account
#
# Private and public keys are stored in { owner => { owner keys } }
# structure (grouped by effective owner).
#
# @!attribute id
# @return [Symbol]
# @!attribute privileges
# @return [Hash{Symbol, Privilege}]
# @!attribute public_keys
# @return [Hash{Symbol, Hash{Symbol, PublicKey}}]
# @!attribute private_keys
# @return [Hash{Symbol, Hash{Symbol, PrivateKey}]
# @!attribute policy
# @return [Symbol] :edit or :manage
- 3
class Account
- 3
include Mixin::Entity
# rubocop:disable Metrics/LineLength
- 3
attribute :id, Symbol
- 3
attribute :privileges, [Hash, K: Symbol, V: Privilege], default: {}
- 3
attribute :public_keys, [Hash, K: Symbol, V: [Hash, K: Symbol, V: PublicKey]], default: {}
- 3
attribute :private_keys, [Hash, K: Symbol, V: [Hash, K: Symbol, V: PrivateKey]], default: {}
- 3
attribute :policy, Policy, default: Policy::NONE
# rubocop:enable Metrics/LineLength
- 3
denormalizer_block do |input, type, context, &block|
- 76
if input.is_a?(Hash) && [:id, 'id'].none? { |key| input.key?(key) }
- 24
input[:id] = context.path.current.name
end
- 26
block.call(input, type, context)
end
- 3
def initialize(id = nil)
- 108
@id = id
- 108
@privileges = {}
- 108
@public_keys = {}
- 108
@private_keys = {}
- 108
@policy = Policy::NONE
end
- 3
def policy=(policy)
- 163
@policy = Policy.wrap(policy)
end
- 3
def public_keys!(client_id)
- 24
public_keys[client_id] = {} unless public_keys.key?(client_id)
- 24
public_keys[client_id]
end
- 3
def private_keys!(client_id)
- 2
private_keys[client_id] = {} unless private_keys.key?(client_id)
- 2
private_keys[client_id]
end
# rubocop:disable Metrics/AbcSize
# @param [Account] other
- 3
def merge(other)
- 18
other.privileges.each do |type, privilege|
- 2
privileges[type] = privilege unless privileges[type]
- 2
privileges[type].options.merge!(privilege.options)
end
- 18
other.public_keys.each do |owner, keys|
- 12
public_keys!(owner).merge!(keys)
end
- 18
other.private_keys.each do |owner, keys|
- 1
private_keys!(owner).merge!(keys)
end
- 18
self.policy = [policy, other.policy].max
- 18
self
end
# rubocop:enable Metrics/AbcSize
- 3
def to_s
"Account :#{id} { policy: :#{policy} }"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../mixin/entity'
- 3
require_relative 'public_key'
- 3
require_relative 'private_key'
- 3
require_relative 'client/role_tree'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
# Represents external node client
- 3
class Client
- 3
include Mixin::Entity
- 3
attribute :id, Symbol
- 3
attribute :roles, RoleTree, default: RoleTree.new
- 3
attribute :public_keys, [Hash, K: Symbol, V: PublicKey]
- 3
attribute :private_keys, [Hash, K: Symbol, V: PrivateKey]
- 3
def initialize(id = nil)
- 136
@id = id
end
- 3
denormalizer_block do |input, type, context, &block|
- 66
input = {} if input.nil?
- 198
if input.is_a?(Hash) && ['id', :id].none? { |key| input.key?(key) }
- 66
input[:id] = context.path.current.name
end
- 66
block.call(input, type, context)
end
- 3
def to_s
"Client :#{id}"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../mixin/entity'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class Client
# Internal structure for client roles
- 3
class RoleTree
- 3
include Mixin::Entity
- 3
attribute :root, [Hash, K: Symbol, V: [:*, NilClass]], default: {}
- 3
normalizer_block do |entity, *|
- 12
entity.root
end
- 3
denormalizer_block do |input, *|
- 66
break RoleTree.new(input) if input.is_a?(Hash)
raise "Expected Hash, received #{input.class}"
end
- 3
def initialize(root = {})
- 139
@root = root
end
- 3
def contains(path)
- 48
cursor = @root
- 48
path.each do |segment|
- 58
return false unless cursor.respond_to?(:key?)
- 58
candidates = [segment.to_s, segment.to_sym]
- 58
next_segment = candidates.find do |candidate|
- 113
cursor.key?(candidate)
end
- 58
return false unless next_segment
- 30
cursor = cursor[next_segment]
end
- 20
true
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'set'
- 3
require_relative '../mixin/entity'
- 3
require_relative 'policy'
- 3
require_relative 'privilege'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
# Represents Linux user group
- 3
class Group
- 3
include Mixin::Entity
- 3
attribute :id, Symbol
- 3
attribute :members, [Set, T: Symbol], default: Set.new
- 3
attribute :privileges, [Hash, K: Symbol, V: Privilege], default: {}
- 3
attribute :policy, Policy, default: Policy::NONE
- 3
denormalizer_block do |input, type, context, &block|
- 28
if input.is_a?(Hash) && [:id, 'id'].none? { |key| input.key?(key) }
- 8
input[:id] = context.path.current.name
end
- 10
block.call(input, type, context)
end
- 3
def initialize(id = nil)
- 41
@id = id
- 41
@members = Set.new
- 41
@privileges = {}
- 41
@policy = Policy::NONE
end
- 3
def policy=(policy)
- 57
@policy = Policy.wrap(policy)
end
# @param [Group] other
- 3
def merge(other)
raise 'Different ids' unless id == other.id
members.merge(other.members)
privileges.merge!(other.privileges)
self.policy = [policy, other.policy].max
self
end
- 3
def to_s
"Group :#{id}"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'ama-entity-mapper'
- 3
require_relative 'privilege'
- 3
require_relative 'partition/filter'
- 3
require_relative 'partition/policy_group'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
# Represents definition of client partition: how clients will be matched
# and what would be done next
- 3
class Partition
- 3
include Mixin::Entity
- 3
attribute :id, Symbol
- 3
attribute :privileges, [Hash, K: Symbol, V: Privilege], default: {}
- 3
attribute :filters, [Enumerable, T: Filter], default: []
- 3
attribute :policy, PolicyGroup, default: PolicyGroup::DEFAULT
- 3
attribute :impersonation, [Hash, K: Symbol, V: [:*, NilClass]]
- 3
denormalizer_block do |input, type, context, &block|
- 87
input = [context.path.current.name] if input.nil?
- 87
if input.is_a?(Enumerable) && !input.is_a?(Hash)
- 11
input = { filters: input }
end
- 261
if input.is_a?(Hash) && ['id', :id].none? { |key| input.key?(key) }
- 87
input[:id] = context.path.current.name
end
- 87
block.call(input, type, context)
end
# @param [AMA::Chef::User::Model::Client] client
- 3
def applies_to(client)
- 68
filters.any? { |filter| filter.apply(client) }
end
- 3
def to_s
"Partition #{id}"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../mixin/entity'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class Partition
# Role filter for matching clients that fit into partition
- 3
class Filter
- 3
include Mixin::Entity
- 3
attribute :roles, [Enumerable, T: [Enumerable, T: String]]
- 3
normalizer_block do |entity, *|
entity.roles.map do |path|
- 15
path.join('.')
- 14
end .join('+')
end
- 3
denormalizer_block do |input, *|
- 72
break input if input.is_a?(Filter.class)
- 72
unless input.is_a?(String)
raise "Expected String, got #{input.class}"
end
- 72
Filter.new.tap do |instance|
- 72
instance.roles = input.split('+').map do |role|
- 75
role.split('.').map(&:strip).map(&:to_sym).reject(&:empty?)
end
end
end
- 3
def initialize(roles = [])
- 144
self.roles = roles
end
# @param [AMA::Chef::User::Model::Client] account
- 3
def apply(account)
- 45
roles.all? do |role|
- 48
account.roles.contains(role)
end
end
- 3
def to_s
"Partition.Filter `#{normalize}`"
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../mixin/entity'
- 3
require_relative '../policy'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class Partition
# Determines how user and group accounts will be treated
- 3
class PolicyGroup
- 3
include Mixin::Entity
- 3
attribute :account, Policy, default: Policy::EDIT
- 3
attribute :group, Policy, default: Policy::EDIT
- 3
DEFAULT = new.tap do |instance|
- 3
instance.account = Policy::EDIT
- 3
instance.group = Policy::EDIT
end
- 3
def to_s
"Partition.Policy {account: #{account}, group: #{group}}"
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../mixin/entity'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
# Helper class for mangling entity policies
- 3
class Policy
- 3
include Comparable
- 3
include Mixin::Entity
- 3
def self.values
- 22
%i[none edit manage]
end
- 3
attribute :value, Symbol, values: values, default: :none
- 3
normalizer_block do |entity, *|
- 359
entity.value
end
- 3
denormalizer_block do |input, type, context, &block|
- 210
if input.is_a?(String) || input.is_a?(Symbol)
- 210
input = { value: input }
end
- 210
block.call(input, type, context)
end
- 3
def initialize(value = :none)
- 327
self.value = value
end
- 3
NONE = new(:none)
- 3
EDIT = new(:edit)
- 3
MANAGE = new(:manage)
- 3
def self.wrap(value)
- 239
return value if value.is_a?(Policy)
- 60
condition = value.is_a?(String) || value.is_a?(Symbol)
- 60
if condition && Policy.const_defined?(value.upcase)
- 60
return Policy.const_get(value.upcase)
end
raise ArgumentError, "Invalid policy value: #{value}"
end
- 3
def edit?
self > NONE
end
- 3
def create?
self > NONE
end
- 3
def remove?
- 80
self == MANAGE
end
- 3
def manage?
self == MANAGE
end
- 3
def nothing?
- 14
self == NONE
end
- 3
def <=>(other)
- 19
other = Policy.wrap(other)
- 19
unless other.is_a?(Policy)
raise ArgumentError, "Invalid input: #{other.inspect}"
end
- 19
values = self.class.values
- 19
values.index(value) - values.index(other.value)
end
- 3
def eq?(other)
- 583
return false unless other.is_a?(Policy)
- 583
other.value == @value
end
- 3
def ==(other)
- 583
eq?(other)
end
- 3
def to_s
value.to_s
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'digest'
- 3
require_relative '../mixin/entity'
- 3
require_relative 'private_key/remote'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class PrivateKey
- 3
include Mixin::Entity
- 3
attribute :id, Symbol
- 3
attribute :owner, Symbol
- 3
attribute :type, Symbol, nullable: true
- 3
attribute :content, String, sensitive: true, nullable: true
- 3
attribute :digest, String
- 3
attribute :public_key, String, sensitive: true, nullable: true
- 3
attribute :passphrase, String, sensitive: true, nullable: true
- 3
attribute :remotes, [Hash, K: Symbol, V: Remote], default: {}
- 3
attribute :validate, TrueClass, FalseClass, default: true
- 3
attribute :install_public_key, TrueClass, FalseClass, default: false
- 3
denormalizer_block do |input, type, context, &block|
- 17
input = { content: input } if input.is_a?(String)
- 17
raise "Hash expected, got #{input.class}" unless input.is_a?(Hash)
- 17
{ id: -1, owner: -3 }.each do |key, index|
- 102
if [key, key.to_s].none? { |v| input.key?(v) }
- 32
segment = context.path.segments[index]
- 32
input[key] = segment.name unless segment.nil?
end
end
- 17
block.call(input, type, context)
end
- 3
normalizer_block do |entity, type, context, &block|
- 22
normalized = block.call(entity, type, context)
- 22
if context.include_sensitive_attributes
normalized[:content] = entity.content
end
- 22
normalized
end
- 3
def content=(content)
- 37
return @content = @digest = nil if content.nil?
- 37
@digest = Digest::MD5.hexdigest(content)
- 37
@content = content
end
- 3
def ==(other)
- 26
return false unless other.is_a?(self.class)
- 9
@id == other.id && @owner == other.owner && @digest == other.digest
end
- 3
def merge(other)
@public_key = other.public_key if other.public_key
@remotes.merge!(other.remotes)
@validate = true if other.validate
@install_public_key = true if other.install_public_key
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../../mixin/entity'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class PrivateKey
- 3
class Remote
- 3
include Mixin::Entity
- 3
attribute :id, Symbol
- 3
attribute :options, [Hash, K: String, V: [String, Integer]]
- 3
denormalizer_block do |input, type, context, &block|
- 8
input = {} if input.nil?
- 8
if input.is_a?(String) || input.is_a?(Symbol)
- 4
input = { User: input }
end
- 8
raise "Expected Hash, got #{input.class}" unless input.is_a?(Hash)
- 8
target = {
options: input[:options] || input['options'] || {},
id: context.path.current.name
}
- 8
['id', :id].each do |key|
- 16
target[:id] = input[key] if input[key]
end
- 8
input.each do |key, value|
- 6
next if %i[id options].include?(key.to_sym)
- 6
target[:options][key] = value
end
- 8
block.call(target, type, context)
end
- 3
def initialize(id = nil)
- 19
@id = id
- 19
@options = {}
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../mixin/entity'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class Privilege
- 3
include Mixin::Entity
- 3
attribute :type, Symbol
- 3
attribute :options, [Hash, K: Symbol, V: [:*, NilClass]], default: {}
- 3
denormalizer_block do |input, type, context, &block|
- 36
input = {} unless input.is_a?(Hash)
- 36
target = {}
- 36
keys = ['type', 'options', :type, :options]
- 36
keys.each do |key|
- 144
target[key.to_sym] = input[key] if input[key]
end
- 36
target[:options] = {} unless target[:options].is_a?(Hash)
- 36
input.each do |key, value|
- 6
next if keys.include?(key)
- 6
target[:options][key] = value
end
- 36
target[:type] = context.path.current.name unless target.key?(:type)
- 36
block.call(target, type, context)
end
- 3
def to_s
"Privilege :#{type}"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'digest'
- 3
require_relative '../mixin/entity'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
- 3
class PublicKey
- 3
include Mixin::Entity
- 3
attribute :owner, Symbol
- 3
attribute :id, Symbol
- 3
attribute :type, Symbol, default: :'ssh-rsa'
- 3
attribute :content, String, sensitive: true, nullable: true
- 3
attribute :digest, String
- 3
attribute :validate, TrueClass, FalseClass, default: true
- 3
denormalizer_block do |input, type, context, &block|
- 47
input = { content: input } if input.is_a?(String)
- 47
raise "Hash expected, got #{input.class}" unless input.is_a?(Hash)
- 47
{ id: -1, owner: -3 }.each do |key, index|
- 282
if [key, key.to_s].none? { |v| input.key?(v) }
- 58
segment = context.path.segments[index]
- 58
input[key] = segment.name unless segment.nil?
end
end
- 47
block.call(input, type, context)
end
- 3
normalizer_block do |entity, type, context, &block|
- 156
normalized = block.call(entity, type, context)
- 156
if context.include_sensitive_attributes
- 1
normalized[:content] = entity.content
end
- 156
normalized
end
- 3
def full_id
raise 'Missing owner' unless @owner
raise 'Missing id' unless @id
"#{@owner}:#{@id}"
end
- 3
def content=(content)
- 101
@digest = content.nil? ? nil : Digest::MD5.hexdigest(content)
- 101
@content = content
end
- 3
def to_s
"PublicKey #{owner}:#{id}"
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative '../mixin/entity'
- 3
require_relative 'account'
- 3
require_relative 'group'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module Model
# Represents complete node state
- 3
class State
- 3
include Mixin::Entity
# !@attribute groups
# @return [Hash{Symbol, Group}]
- 3
attribute :groups, [Hash, K: Symbol, V: Group], default: {}
# !@attribute accounts
# @return [Hash{Symbol, Account}]
- 3
attribute :accounts, [Hash, K: Symbol, V: Account], default: {}
# !@attribute version
# @return [Integer]
- 3
attribute :version, Integer, default: 1
- 3
def initialize
- 92
@groups = {}
- 92
@accounts = {}
- 92
@version = 1
end
# @return [AMA::Chef::User::Model::Account]
- 3
def account!(id)
- 18
id = id.to_sym
- 18
unless accounts[id]
- 15
accounts[id] = Account.new
- 15
accounts[id].id = id
- 15
accounts[id].policy = Policy::NONE
end
- 18
account(id)
end
# @return [AMA::Chef::User::Model::Account]
- 3
def account(id)
- 18
accounts[id.to_sym]
end
# @return [AMA::Chef::User::Model::Group]
- 3
def group!(id)
id = id.to_sym
unless groups[id]
groups[id] = Group.new
groups[id].id = id
end
group(id)
end
# @return [AMA::Chef::User::Model::Group]
- 3
def group(id)
- 5
groups[id.to_sym]
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'set'
- 3
::Dir.glob("#{__dir__}/action/**/*.rb").each do |path|
- 63
require path
end
- 3
require_relative 'planner/account'
- 3
require_relative 'planner/group'
- 3
module AMA
- 3
module Chef
- 3
module User
# This class is responsible for creating list of actions required for
# converging from current state to target state
- 3
class Planner
- 3
def initialize
- 10
@accounts = Account.new
- 10
@groups = Group.new
end
# @param [AMA::Chef::User::Model::State] current_state
# @param [AMA::Chef::User::Model::State] desired_state
- 3
def plan(current_state, desired_state)
- 18
plan = [
*@accounts.plan(current_state.accounts, desired_state.accounts),
*@groups.plan(current_state.groups, desired_state.groups)
]
- 18
plan.each do |action|
- 119
action.class_name = action.class.to_s
end
- 18
plan
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative 'account/public_key'
- 3
require_relative 'account/private_key'
- 3
require_relative 'account/privilege'
- 3
require_relative '../action/account/create'
- 3
require_relative '../action/account/remove'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
# This planner creates actions altering account state
- 3
class Account
- 3
def initialize
- 16
@public_keys = PublicKey.new
- 16
@private_keys = PrivateKey.new
- 16
@privileges = Privilege.new
end
# @param [Hash{Symbol, AMA::Chef::User::Model::Account}] current_state
# @param [Hash{Symbol, AMA::Chef::User::Model::Account}] desired_state
- 3
def plan(current_state, desired_state)
- 24
(current_state.keys | desired_state.keys).flat_map do |id|
- 32
process(current_state[id], desired_state[id])
end
end
# @param [AMA::Chef::User::Model::Account] current_state
# @param [AMA::Chef::User::Model::Account] desired_state
- 3
def process(current_state, desired_state)
- 32
actions = [
*process_public_keys(current_state, desired_state),
*process_private_keys(current_state, desired_state),
*process_privileges(current_state, desired_state)
]
- 32
if !desired_state.nil?
- 16
actions.unshift(ns::Create.new(desired_state))
- 16
elsif current_state.policy.remove?
- 10
actions.push(ns::Remove.new(current_state))
end
- 32
actions
end
- 3
private
- 3
def ns
- 26
::AMA::Chef::User::Action::Account
end
# @param [AMA::Chef::User::Model::Account] current_state
# @param [AMA::Chef::User::Model::Account] desired_state
- 3
def process_public_keys(current_state, desired_state)
- 32
return [] if desired_state.nil? && !current_state.policy.remove?
- 26
account = desired_state || current_state
- 26
current_keys = current_state ? current_state.public_keys : {}
- 26
desired_keys = desired_state ? desired_state.public_keys : {}
- 26
@public_keys.plan(account, current_keys, desired_keys)
end
# @param [AMA::Chef::User::Model::Account] current_state
# @param [AMA::Chef::User::Model::Account] desired_state
- 3
def process_private_keys(current_state, desired_state)
- 32
return [] if desired_state.nil? && !current_state.policy.remove?
- 26
account = desired_state || current_state
- 26
current_keys = current_state ? current_state.private_keys : {}
- 26
desired_keys = desired_state ? desired_state.private_keys : {}
- 26
@private_keys.plan(account, current_keys, desired_keys)
end
# @param [AMA::Chef::User::Model::Account] current_state
# @param [AMA::Chef::User::Model::Account] desired_state
- 3
def process_privileges(current_state, desired_state)
- 32
return [] if desired_state.nil? && !current_state.policy.remove?
- 26
account = desired_state || current_state
- 26
current_privileges = current_state ? current_state.privileges : {}
- 26
desired_privileges = desired_state ? desired_state.privileges : {}
- 26
@privileges.plan(account, current_privileges, desired_privileges)
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
- 3
require_relative 'private_key/remote'
- 3
require_relative '../../action/account/private_key/add'
- 3
require_relative '../../action/account/private_key/remove'
- 3
require_relative '../../action/account/private_key/purge'
- 3
require_relative '../../action/account/public_key/add'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
- 3
class Account
# This planner creates actions altering account private keys
- 3
class PrivateKey
- 3
def initialize
- 20
@remote = Remote.new
end
# @param [AMA::Chef::User::Model::Account] account
# @param [Hash{Symbol, Hash{Symbol, AMA::Chef::User::Model::PrivateKey}}] current_state
# @param [Hash{Symbol, Hash{Symbol, AMA::Chef::User::Model::PrivateKey}}] desired_state
- 3
def plan(account, current_state, desired_state)
- 30
owners = current_state.keys | desired_state.keys
- 30
actions = owners.flat_map do |owner|
- 5
current_keys = current_state[owner] || {}
- 5
desired_keys = desired_state[owner] || {}
- 5
(current_keys.keys | desired_keys.keys).flat_map do |key|
- 5
process(account, current_keys[key], desired_keys[key])
end
end
- 30
actions.push(ns::Purge.new(account)) if desired_state.empty?
- 30
actions
end
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] current_state
# @param [AMA::Chef::User::Model::PrivateKey] desired_state
- 3
def process(account, current_state, desired_state)
- 5
current_remotes = current_state ? current_state.remotes : {}
- 5
desired_remotes = desired_state ? desired_state.remotes : {}
- 5
key = desired_state || current_state
- 5
actions = @remote.plan(
account,
key,
current_remotes,
desired_remotes
)
- 5
if desired_state.nil?
- 2
actions.push(ns::Remove.new(account, current_state))
else
- 3
actions.unshift(ns::Add.new(account, desired_state))
end
- 5
actions
end
- 3
def ns
- 32
::AMA::Chef::User::Action::Account::PrivateKey
end
- 3
def pubkey_ns
::AMA::Chef::User::Action::Account::PublicKey
end
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
- 3
require_relative '../../../action/account/private_key/remote/add'
- 3
require_relative '../../../action/account/private_key/remote/remove'
- 3
require_relative '../../../action/account/private_key/remote/purge'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
- 3
class Account
- 3
class PrivateKey
# This planner creates actions altering private key remotes
- 3
class Remote
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] private_key
# @param [Hash{Symbol, AMA::Chef::User::Model::PrivateKey::Remote}] current_state
# @param [Hash{Symbol, AMA::Chef::User::Model::PrivateKey::Remote}] desired_state
- 3
def plan(account, private_key, current_state, desired_state)
- 8
current_state ||= {}
- 8
desired_state ||= {}
- 8
actions = (current_state.keys | desired_state.keys).map do |key|
- 3
current = current_state[key]
- 3
desired = desired_state[key]
- 3
process(account, private_key, current, desired)
end
- 8
if desired_state.empty?
- 6
actions.push(ns::Purge.new(account, private_key))
end
- 8
actions
end
- 3
private
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PrivateKey] private_key
# @param [AMA::Chef::User::Model::PrivateKey::Remote] current_state
# @param [AMA::Chef::User::Model::PrivateKey::Remote] desired_state
- 3
def process(account, private_key, current_state, desired_state)
- 3
if desired_state.nil?
- 1
ns::Remove.new(account, private_key, current_state)
else
- 2
ns::Add.new(account, private_key, desired_state)
end
end
- 3
def ns
- 9
::AMA::Chef::User::Action::Account::PrivateKey::Remote
end
end
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
- 3
require_relative '../../action/account/privilege/grant'
- 3
require_relative '../../action/account/privilege/revoke'
- 3
require_relative '../../action/account/privilege/purge'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
- 3
class Account
# This planner creates actions altering account state
- 3
class Privilege
# @param [AMA::Chef::User::Model::Account] account
# @param [Hash{Symbol, AMA::Chef::User::Model::Privilege}] current_state
# @param [Hash{Symbol, AMA::Chef::User::Model::Privilege}] desired_state
- 3
def plan(account, current_state, desired_state)
- 30
actions = (current_state.keys | desired_state.keys).map do |key|
- 7
process(account, current_state[key], desired_state[key])
end
- 30
actions.push(ns::Purge.new(account)) if desired_state.empty?
- 30
actions
end
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::Privilege] current_state
# @param [AMA::Chef::User::Model::Privilege] desired_state
- 3
def process(account, current_state, desired_state)
- 7
if desired_state.nil?
- 3
ns::Revoke.new(account, current_state)
else
- 4
ns::Grant.new(account, desired_state)
end
end
- 3
def ns
- 33
::AMA::Chef::User::Action::Account::Privilege
end
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
- 3
require_relative '../../action/account/public_key/add'
- 3
require_relative '../../action/account/public_key/remove'
- 3
require_relative '../../action/account/public_key/purge'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
- 3
class Account
# This planner creates actions altering account privileges
- 3
class PublicKey
# @param [AMA::Chef::User::Model::Account] account
# @param [Hash{Symbol, Hash{Symbol, AMA::Chef::User::Model::PublicKey}}] current_state
# @param [Hash{Symbol, Hash{Symbol, AMA::Chef::User::Model::PublicKey}}] desired_state
- 3
def plan(account, current_state, desired_state)
- 31
owners = current_state.keys | desired_state.keys
- 31
actions = owners.flat_map do |owner|
- 21
current_keys = current_state[owner] || {}
- 21
desired_keys = desired_state[owner] || {}
- 21
(current_keys.keys | desired_keys.keys).map do |key|
- 23
process(account, current_keys[key], desired_keys[key])
end
end
- 31
actions.push(ns::Purge.new(account)) if desired_state.empty?
- 31
actions
end
# @param [AMA::Chef::User::Model::Account] account
# @param [AMA::Chef::User::Model::PublicKey] current_state
# @param [AMA::Chef::User::Model::PublicKey] desired_state
- 3
def process(account, current_state, desired_state)
- 23
if desired_state.nil?
- 7
ns::Remove.new(account, current_state)
else
- 16
ns::Add.new(account, desired_state)
end
end
- 3
def ns
- 41
::AMA::Chef::User::Action::Account::PublicKey
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require_relative 'group/privilege'
- 3
require_relative '../action/group/set_members'
- 3
require_relative '../action/group/append_members'
- 3
require_relative '../action/group/exclude_members'
- 3
require_relative '../action/group/remove'
- 3
require_relative '../model/policy'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
# This planner creates actions altering group state
- 3
class Group
- 3
def initialize
- 19
@privilege = Privilege.new
end
# @param [Hash{Symbol, AMA::Chef::User::Model::Group}] current_state
# @param [Hash{Symbol, AMA::Chef::User::Model::Group}] desired_state
- 3
def plan(current_state, desired_state)
- 27
(current_state.keys | desired_state.keys).flat_map do |id|
- 18
process(current_state[id], desired_state[id])
end
end
- 3
private
# @param [AMA::Chef::User::Model::Group] current_state
# @param [AMA::Chef::User::Model::Group] desired_state
- 3
def process(current_state, desired_state)
- 18
actions = privilege_actions(current_state, desired_state)
- 18
group = desired_state || current_state
- 18
return [] if group.policy == Model::Policy::NONE
- 18
if desired_state.nil?
- 8
actions.push(*deletion_actions(current_state))
else
- 10
actions.push(*creation_actions(current_state, desired_state))
end
- 18
post_process_actions(actions)
end
- 3
def ns
- 19
::AMA::Chef::User::Action::Group
end
- 3
def creation_actions(current_state, desired_state)
- 10
group = desired_state
- 10
unless desired_state.policy == Model::Policy::EDIT
- 5
return [ns::SetMembers.new(group)]
end
- 5
actions = [ns::AppendMembers.new(group)]
- 5
current_members = current_state ? current_state.members : Set.new
- 5
excluded_members = current_members - desired_state.members
- 5
return actions if excluded_members.empty?
- 1
actions.unshift(ns::ExcludeMembers.new(group, excluded_members))
end
- 3
def deletion_actions(current_state)
- 8
if current_state.policy.remove?
- 5
return [ns::Remove.new(current_state)]
end
- 3
return [] if current_state.members.empty?
- 3
[ns::ExcludeMembers.new(current_state, current_state.members)]
end
- 3
def post_process_actions(actions)
- 18
actions.each do |action|
- 36
action.class_name = action.class.to_s
end
- 18
actions
end
- 3
def privilege_actions(current_state, desired_state)
- 18
return [] if desired_state.nil? && !current_state.policy.remove?
- 15
group = desired_state || current_state
- 15
current = current_state.nil? ? {} : current_state.privileges
- 15
desired = desired_state.nil? ? {} : desired_state.privileges
- 15
@privilege.plan(group, current, desired)
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
- 3
require_relative '../../action/group/privilege/grant'
- 3
require_relative '../../action/group/privilege/revoke'
- 3
require_relative '../../action/group/privilege/purge'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
class Planner
- 3
class Group
# Planner for group privileges
- 3
class Privilege
# @param [AMA::Chef::User::Model::Group] group
# @param [Hash{Symbol, AMA::Chef::User::Model::Privilege}] current_state
# @param [Hash{Symbol, AMA::Chef::User::Model::Privilege}] desired_state
- 3
def plan(group, current_state, desired_state)
- 19
actions = (current_state.keys | desired_state.keys).map do |type|
- 7
process(group, current_state[type], desired_state[type])
end
- 19
actions.push(ns::Purge.new(group)) if desired_state.empty?
- 19
actions
end
- 3
private
# @param [AMA::Chef::User::Model::Group] group
# @param [AMA::Chef::User::Model::Privilege] current_state
# @param [AMA::Chef::User::Model::Privilege] desired_state
- 3
def process(group, current_state, desired_state)
- 7
return ns::Revoke.new(group, current_state) if desired_state.nil?
- 4
ns::Grant.new(group, desired_state)
end
- 3
def ns
- 22
::AMA::Chef::User::Action::Group::Privilege
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'set'
- 3
require_relative '../model/policy'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module State
# Simple class that builds target state by applying partitions to
# clients
- 3
class Builder
# @param [Hash{Symbol, AMA::Chef::User::Model::Client}] clients
# @param [Hash{Symbol, AMA::Chef::User::Model::Partition}] partitions
# @return [AMA::Chef::User::Model::State]
- 3
def build(clients, partitions)
- 9
state = Model::State.new
- 9
partitions.values.each do |partition|
- 13
process_partition(state, partition, clients)
end
- 9
clean_state(state)
- 9
state
end
- 3
private
# @param [AMA::Chef::User::Model::State] state
# @param [AMA::Chef::User::Model::Partition] partition
# @param [Hash<Symbol, AMA::Chef::User::Model::Client>] clients
- 3
def process_partition(state, partition, clients)
- 13
unless partition.policy.group == Model::Policy::NONE
- 5
state.groups[partition.id] = extract_group(partition)
end
- 13
clients.values.each do |client|
- 30
next unless partition.applies_to(client)
- 14
apply_partition(state, client, partition)
end
end
# @param [AMA::Chef::User::Model::State] state
# @param [AMA::Chef::User::Model::Client] client
# @param [AMA::Chef::User::Model::Partition] partition
- 3
def apply_partition(state, client, partition)
- 14
account = extract_account(client, partition)
- 14
state.account!(client.id).merge(account)
- 14
apply_impersonation(state, client, partition)
- 14
return unless partition.policy.group != Model::Policy::NONE
- 5
state.group(partition.id).members.add(account.id)
end
# @param [AMA::Chef::User::Model::State] state
- 3
def clean_state(state)
- 9
state.accounts.reject! do |_, account|
- 15
account.policy == Model::Policy::NONE
end
- 9
state.groups.reject! do |_, group|
- 5
group.policy == Model::Policy::NONE
end
- 9
state
end
- 3
def extract_group(partition)
- 5
group = Model::Group.new(partition.id)
- 5
group.policy = partition.policy.group
- 5
group.privileges = partition.privileges.clone
- 5
group
end
# rubocop:disable Metrics/AbcSize
- 3
def extract_account(client, partition)
- 14
account = Model::Account.new(client.id)
- 14
account.policy = partition.policy.account
- 14
unless client.public_keys.empty?
- 8
account.public_keys!(client.id).merge!(client.public_keys)
end
- 14
unless client.private_keys.empty?
- 1
account.private_keys!(client.id).merge!(client.private_keys)
end
- 14
if partition.policy.group.nothing? && !partition.privileges.empty?
- 2
account.privileges = partition.privileges.clone
end
- 14
account
end
# rubocop:enable Metrics/AbcSize
- 3
def apply_impersonation(state, client, partition)
- 14
return if client.public_keys.empty?
- 8
partition.impersonation.keys.each do |hijacked|
- 4
package = Model::Account.new(hijacked)
- 4
package.policy = :edit
- 4
package.public_keys!(client.id).merge!(client.public_keys)
- 4
state.account!(hijacked).merge(package)
end
end
end
end
end
end
end
# frozen_string_literal: true
- 3
require 'ama-entity-mapper'
- 3
require_relative '../model/account'
- 3
require_relative '../model/group'
- 3
require_relative '../model/state'
- 3
module AMA
- 3
module Chef
- 3
module User
- 3
module State
# Saves data as node attributes
- 3
class Persister
- 3
attr_reader :node
# @param [Chef::Node] node
- 3
def initialize(node)
- 1
@node = node
end
- 3
def retrieve(context_name)
data = fetch(context_name) || {}
Entity::Mapper.map(data, AMA::Chef::User::Model::State)
end
- 3
def persist(context_name, state)
- 1
save([context_name], Entity::Mapper.normalize(state))
end
- 3
private
- 3
def save(path, data)
- 1
data = data.normalize if data.respond_to? :normalize
- 1
write_handle(*path[0..-2])[path[-1]] = data
end
- 3
def fetch(*path)
read_handle(*path)
end
- 3
def delete(*path)
return if path.empty?
path = [path] if path.is_a?(String)
return if read_handle(path).nil?
write_handle(path[0..-2]).delete(path[-1])
end
- 3
def expand_path(*path)
- 1
%w[ama user state].push(*path)
end
- 3
def read_handle(*path)
expand_path(*path).reduce(node) do |cursor, segment|
cursor && cursor.key?(segment) ? cursor[segment] : nil
end
end
- 3
def write_handle(*path)
- 1
expand_path(*path).reduce(node.normal) do |cursor, segment|
- 3
cursor[segment] = {} unless cursor.key?(segment)
- 3
cursor[segment]
end
end
end
end
end
end
end