Class: QB::Role
- Inherits:
-
Object
- Object
- QB::Role
- Defined in:
- lib/qb/role.rb,
lib/qb/role/errors.rb
Overview
Contains info on a QB role.
Defined Under Namespace
Classes: MetadataError, MultipleMatchesError, NoMatchesError
Constant Summary collapse
- BUILTIN_PATH =
Array of string paths to directories to search for roles or paths to
ansible.cfgfiles to look for an extract role paths from.For the moment at least you can just mutate this value like you would
$LOAD_PATH:QB::Role::PATH.unshift '~/where/some/roles/be' QB::Role::PATH.unshift '~/my/ansible.cfg'The paths are searched from first to last.
WARNING
Search is deep - don't point this at large directory trees and expect any sort of reasonable performance (any directory that contains
node_modulesis usually a terrible idea for instance). [ # Development Paths # ================= # # These come first because: # # 1. They are working dir-local. # # 2. They should only be present in local development, and should be # capable of overriding roles in other local directories to allow # custom development behavior (the same way `./dev/bin` is put in # front or `./bin`). # # Role paths declared in ./dev/ansible.cfg, if it exists. File.join('.', 'dev', 'ansible.cfg'), # Roles in ./dev/roles File.join('.', 'dev', 'roles'), # Working Directory Paths # ======================= # # Next up, `ansible.cfg` and `roles` directory in the working dir. # Makes sense, right? # # ./ansible.cfg File.join('.', 'ansible.cfg'), # ./roles File.join('.', 'roles'), # Working Directory-Local Ansible Directory # ========================================= # # `ansible.cfg` and `roles` in a `./ansible` directory, making a common # place to put Ansible stuff in an project accessible when running from # the project root. # # ./ansible/ansible.cfg File.join('.', 'ansible', 'ansible.cfg'), # ./ansible/roles File.join('.', 'ansible', 'roles'), # TODO Git repo root relative? # Some sort of flag file for a find-up? # System Ansible locations? # QB Gem Role Directories # ======================= # # Last, but far from least, paths provided by the QB Gem to the user's # QB role install location and the roles that come built-in to the gem. QB::USER_ROLES_DIR, QB::GEM_ROLES_DIR, ].freeze
- PATH =
Array of string paths to directories to search for roles or paths to
ansible.cfgfiles to look for an extract role paths from.Value is a duplicate of the frozen BUILTIN_PATH. You can reset to those values at any time via reset_path!.
For the moment at least you can just mutate this value like you would
$LOAD_PATH:QB::Role::PATH.unshift '~/where/some/roles/be' QB::Role::PATH.unshift '~/my/ansible.cfg'The paths are searched from first to last.
WARNING
Search is deep - don't point this at large directory trees and expect any sort of reasonable performance (any directory that contains
node_modulesis usually a terrible idea for instance). BUILTIN_PATH.dup
Instance Attribute Summary collapse
-
#display_path ⇒ Object
readonly
the path to the role that we display.
-
#meta_path ⇒ Object
readonly
Returns the value of attribute meta_path.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Class Method Summary collapse
-
.available ⇒ Object
array of QB::Role found in search path.
-
.get_include_path(role, option_meta, current_include_path) ⇒ Array<string>
Get the include path for an included role based on the option metadata that defines the include and the current include path.
- .guess_role_name(dest) ⇒ Object
-
.matches(input) ⇒ Array<QB::Role>
Get an array of QB::Role that match an input string.
-
.require(input) ⇒ QB::Role
Find exactly one matching role for the input string or raise.
-
.reset_path! ⇒ Array<String>
Reset PATH to the original built-in values in BUILTIN_PATH.
-
.role_dir?(pathname) ⇒ Boolean
true if pathname is a QB role directory.
- .roles_paths(dir) ⇒ Object
-
.search_path ⇒ Array<Pathname>
places to look for qb role directories.
-
.to_display_path(path) ⇒ Pathname
The path we display in the CLI, see #display_path.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#ask_vault_pass? ⇒ Boolean
should qb ask for an ansible vault password?.
-
#banner ⇒ Object
get the CLI banner for the role.
-
#default_ansible_options ⇒ Hash<String, *>
Default
ansible-playbookCLI options from role qb metadata. -
#default_dir(cwd, options) ⇒ Object
gets the default
qb_dirvalue, raising an error if the role doesn't define how to get one or there is a problem getting it. -
#defaults ⇒ Object
gets the role variable defaults from defaults/main.yml, or {}.
- #examples ⇒ Object
-
#format_examples ⇒ String
format the
meta.exampleshash into a string suitable for cli output. -
#hash ⇒ Object
Language Inter-Op -----------------------------------------------------------------------.
-
#initialize(path, search_dir: nil) ⇒ Role
constructor
Instantiate a Role.
-
#load_defaults(cache = true) ⇒ Object
loads the defaults from vars/main.yml and defaults/main.yml, caching by default.
-
#load_meta(cache = true) ⇒ Object
load qb metadata from meta/qb.yml or from executing meta/qb and parsing the YAML written to stdout.
-
#meta ⇒ Hash{String => Object}
The QB metadata for the role.
-
#mkdir ⇒ Object
if the exe should auto-make the directory.
-
#namespace ⇒ Object
Instance Methods =====================================================================.
- #namespaceless ⇒ Object
-
#option_metas ⇒ Object
get the options from the metadata, defaulting to [] if none defined.
-
#options(include_path = []) ⇒ Array<QB::Options::Option> an array of Option for the role, including any included roles.
ArrayQB::Options::Option an array of Option for the role, including any included roles.
- #options_key ⇒ Object
-
#puts_examples ⇒ Object
examples text.
-
#qb_requirement ⇒ Gem::Requirement?
Get the Gem::Requirement parse of the
qb_requirementkey in #meta (if it is defined), which specifies the required version ofqbfor the role. - #save_options ⇒ Object
- #to_s ⇒ String
-
#usage ⇒ String
Usage information formatted as plain text for the CLI.
- #uses_default_dir? ⇒ Boolean
-
#var_prefix ⇒ Object
gets the variable prefix that will be appended to cli options before passing them to the role.
Constructor Details
#initialize(path, search_dir: nil) ⇒ Role
Instantiate a Role.
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/qb/role.rb', line 492 def initialize path, search_dir: nil @path = if path.is_a?(Pathname) then path else Pathname.new(path) end # check it... unless @path.exist? raise Errno::ENOENT.new @path.to_s end unless @path.directory? raise Errno::ENOTDIR.new @path.to_s end @display_path = self.class.to_display_path @path @meta_path = if (@path + 'meta' + 'qb').exist? @path + 'meta' + 'qb' elsif (@path + 'meta' + 'qb.yml').exist? @path + 'meta' + 'qb.yml' else raise Errno::ENOENT.new "#{ @path.join('meta').to_s }/[qb|qb.yml]" end if search_dir.nil? @name = @path.to_s.split(File::SEPARATOR).last else @name = @path.relative_path_from(search_dir).to_s end end |
Instance Attribute Details
#display_path ⇒ Object (readonly)
the path to the role that we display. we only show the directory name
for QB roles, and use Util.compact_path to show . and ~ for
paths relative to the current directory and home directory, respectively.
@return [Pathname]
469 470 471 |
# File 'lib/qb/role.rb', line 469 def display_path @display_path end |
#meta_path ⇒ Object (readonly)
Returns the value of attribute meta_path.
476 477 478 |
# File 'lib/qb/role.rb', line 476 def @meta_path end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
459 460 461 |
# File 'lib/qb/role.rb', line 459 def name @name end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
453 454 455 |
# File 'lib/qb/role.rb', line 453 def path @path end |
Class Method Details
.available ⇒ Object
array of QB::Role found in search path.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/qb/role.rb', line 215 def self.available search_path. select {|search_dir| # make sure it's there (and a directory) search_dir.directory? }. map {|search_dir| Pathname.glob(search_dir.join '**', 'meta', 'qb.yml'). map {|| [.dirname.dirname, search_dir: search_dir] } }. flatten(1). map {|args| QB::Role.new *args }. uniq end |
.get_include_path(role, option_meta, current_include_path) ⇒ Array<string>
Get the include path for an included role based on the option metadata that defines the include and the current include path.
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/qb/role.rb', line 371 def self.get_include_path role, , current_include_path new_include_path = if .key? 'as' case ['as'] when nil, false # include it in with the parent role's options current_include_path when String current_include_path + [['as']] else raise QB::Role::MetadataError.new, "bad 'as' value: #{ .inspect }" end else current_include_path + [role.namespaceless] end end |
.guess_role_name(dest) ⇒ Object
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/qb/role.rb', line 408 def self.guess_role_name dest raise "TODO: Not implemented!!!" path = QB::Util.resolve dest search_dirs = search_path.find_all { |pathname| path.fnmatch?( pathname / '**' ) } case search_dirs.length when 0 # It's not in any of the search directories # # If it has 'roles' as a segment than use what's after the last occurrence # of that (unless there isn't anything). # segments = path.to_s.split File::SEPARATOR if index = segments.rindex( 'roles' ) name_segs = segments[index..-1] unless name_segs.empty? return File.join name_segs end end # Ok, that didn't work... just return the basename I guess... File.basename path when 1 else # Multiple matches?!?!? end end |
.matches(input) ⇒ Array<QB::Role>
Get an array of QB::Role that match an input string.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/qb/role.rb', line 238 def self.matches input # keep this here to we don't re-gen every loop available = self.available # first off, see if input matches any relative paths exactly available.each {|role| return [role] if role.display_path.to_s == input } # create an array of "separator" variations to try *exact* matching # against. in order of preference: # # 1. exact input # - this means if you ended up with roles that actually *are* # differnetiated by '_/-' differences (which, IMHO, is a # horrible fucking idea), you can get exactly what you ask for # as a first priority # 2. input with '-' changed to '_' # - prioritized because convetion is to underscore-separate # role names. # 3. input with '_' changed to '-' # - really just for convience's sake so you don't really have to # remember what separator is used. # separator_variations = [ input, input.gsub('-', '_'), input.gsub('_', '-'), ] separator_variations.each { |variation| available.each { |role| # exact match to full name return [role] if role.name == variation }.each { |role| # exact match without the namespace prefix ('qb.' or similar) return [role] if role.namespaceless == variation } } # see if we prefix match any full names separator_variations.each { |variation| name_prefix_matches = available.select { |role| role.name.start_with? variation } return name_prefix_matches unless name_prefix_matches.empty? } # see if we prefix match any name separator_variations.each { |variation| namespaceless_prefix_matches = available.select { |role| role.namespaceless.start_with? variation } unless namespaceless_prefix_matches.empty? return namespaceless_prefix_matches end } # see if we prefix match any display paths separator_variations.each { |variation| path_prefix_matches = available.select { |role| role.display_path.start_with? variation } return path_prefix_matches unless path_prefix_matches.empty? } # see if we word match any display paths name_word_matches = available.select { |role| QB::Util.words_start_with? role.display_path.to_s, input } return name_word_matches unless name_word_matches.empty? # nada [] end |
.require(input) ⇒ QB::Role
Find exactly one matching role for the input string or raise.
Where we look is determined by PATH via search_path.
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/qb/role.rb', line 331 def self.require input as_pathname = Pathname.new(input) # allow a path to a role dir if role_dir? as_pathname return QB::Role.new as_pathname end matches = self.matches input role = case matches.length when 0 raise QB::Role::NoMatchesError.new input when 1 matches[0] else raise QB::Role::MultipleMatchesError.new input, matches end QB.debug "role match" => role role end |
.reset_path! ⇒ Array<String>
Reset PATH to the original built-in values in BUILTIN_PATH.
Created for testing but might be useful elsewhere as well.
153 154 155 156 157 |
# File 'lib/qb/role.rb', line 153 def self.reset_path! PATH.clear BUILTIN_PATH.each { |path| PATH << path } PATH end |
.role_dir?(pathname) ⇒ Boolean
true if pathname is a QB role directory.
161 162 163 164 165 166 |
# File 'lib/qb/role.rb', line 161 def self.role_dir? pathname # must be a directory pathname.directory? && # and must have meta/qb.yml or meta/qb file ['qb.yml', 'qb'].any? {|filename| pathname.join('meta', filename).file?} end |
.roles_paths(dir) ⇒ Object
170 171 172 173 174 175 |
# File 'lib/qb/role.rb', line 170 def self.roles_paths dir cfg_roles_path(dir) + [ dir.join('roles'), dir.join('roles', 'tmp'), ] end |
.search_path ⇒ Array<Pathname>
places to look for qb role directories. these paths are also included when qb runs a playbook.
TODO resolution order:
- paths specific to this run: a. TODO paths provided on the cli.
- paths specific to the current directory: a. paths specified in ./ansible.cfg (if it exists) b. ./roles d. paths specified in ./ansible/ansible.cfg (if it exists) e. ./ansible/roles g. paths specified in ./dev/ansible.cfg (if it exists) h. ./dev/roles i. ./dev/roles/tmp - used for roles that are downloaded but shouldn't be included in source control. 3.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/qb/role.rb', line 199 def self.search_path QB::Role::PATH. map { |path| if QB::Ansible::ConfigFile.end_with_config_file?(path) if File.file?(path) QB::Ansible::ConfigFile.new(path).defaults.roles_path end else QB::Util.resolve path end }. flatten. reject(&:nil?) end |
.to_display_path(path) ⇒ Pathname
The path we display in the CLI, see #display_path.
397 398 399 400 401 402 403 |
# File 'lib/qb/role.rb', line 397 def self.to_display_path path if path.realpath.start_with? QB::GEM_ROLES_DIR path.realpath.sub (QB::GEM_ROLES_DIR.to_s + '/'), '' else QB::Util.contract_path path end end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
898 899 900 |
# File 'lib/qb/role.rb', line 898 def == other other.is_a?(self.class) && other.path.realpath == path.realpath end |
#ask_vault_pass? ⇒ Boolean
should qb ask for an ansible vault password?
763 764 765 |
# File 'lib/qb/role.rb', line 763 def ask_vault_pass? !!@meta['ask_vault_pass'] end |
#banner ⇒ Object
get the CLI banner for the role
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 |
# File 'lib/qb/role.rb', line 696 def lines = [] name_line = "#{ name } role" lines << name_line lines << "=" * name_line.length lines << '' if ['description'] lines << ['description'] lines << '' end lines << 'usage:' lines << " #{ usage }" lines << '' lines << 'options:' lines.join("\n") end |
#default_ansible_options ⇒ Hash<String, *>
Returns default ansible-playbook CLI options from role qb metadata.
Hash of option name to value.
869 870 871 |
# File 'lib/qb/role.rb', line 869 def 'ansible_options', {} end |
#default_dir(cwd, options) ⇒ Object
gets the default qb_dir value, raising an error if the role doesn't
define how to get one or there is a problem getting it.
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 |
# File 'lib/qb/role.rb', line 780 def default_dir cwd, QB.debug "get_default_dir", role: self, meta: self., cwd: cwd, options: key = 'default_dir' value = self.[key] case value when nil # there is no get_dir info in meta/qb.yml, can't get the dir raise QB::UserInputError.dedented <<-END unable to infer default directory: no '#{ key }' key in 'meta/qb.yml' for role #{ self } END when false # this method should not get called when the value is false (an entire # section is skipped in exe/qb when `default_dir = false`) raise QB::StateError.squished <<-END role does not use default directory (meta/qb.yml:default_dir = false) END when 'git_root' QB.debug "returning the git root relative to cwd" NRSER.git_root cwd when 'cwd' QB.debug "returning current working directory" cwd when Hash QB.debug "qb meta option is a Hash" unless value.length == 1 raise "#{ .to_s }:default_dir invalid: #{ value.inspect }" end hash_key, hash_value = value.first case hash_key when 'exe' exe_path = hash_value # supply the options to the exe so it can make work off those values # if it wants. exe_input_data = Hash[ .map {|option| [option.cli_option_name, option.value] } ] unless exe_path.start_with?('~') || exe_path.start_with?('/') exe_path = File.join(self.path, exe_path) debug 'exe path is relative, basing off role dir', exe_path: exe_path end debug "found 'exe' key, calling", exe_path: exe_path, exe_input_data: exe_input_data Cmds.chomp! exe_path do JSON.dump exe_input_data end when 'find_up' filename = hash_value unless filename.is_a? String raise "find_up filename must be string, found #{ filename.inspect }" end QB.debug "found 'find_up', looking for file named #{ filename }" QB::Util.find_up filename else raise QB::Role::MetadataError.squised <<-END bad key: #{ hash_key } in #{ self..to_s }:default_dir END end end end |
#defaults ⇒ Object
gets the role variable defaults from defaults/main.yml, or {}
647 648 649 |
# File 'lib/qb/role.rb', line 647 def defaults @defaults || load_defaults end |
#examples ⇒ Object
716 717 718 |
# File 'lib/qb/role.rb', line 716 def examples @meta['examples'] end |
#format_examples ⇒ String
format the meta.examples hash into a string suitable for cli
output.
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 |
# File 'lib/qb/role.rb', line 726 def format_examples examples. map {|title, body| [ "#{ title }:", body.lines.map {|l| # only indent non-empty lines # makes compacting newline sequences easier (see below) if l.match(/^\s*$/) l else ' ' + l end }, '' ] }. flatten. join("\n"). # compact newline sequences gsub(/\n\n+/, "\n\n") end |
#hash ⇒ Object
Language Inter-Op
893 894 895 |
# File 'lib/qb/role.rb', line 893 def hash path.realpath.hash end |
#load_defaults(cache = true) ⇒ Object
loads the defaults from vars/main.yml and defaults/main.yml, caching by default. vars override defaults values.
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
# File 'lib/qb/role.rb', line 622 def load_defaults cache = true defaults_path = @path + 'defaults' + 'main.yml' defaults = if defaults_path.file? YAML.load(defaults_path.read) || {} else {} end vars_path = @path + 'vars' + 'main.yml' vars = if vars_path.file? YAML.load(vars_path.read) || {} else {} end defaults = defaults.merge! vars if cache @defaults = defaults end defaults end |
#load_meta(cache = true) ⇒ Object
load qb metadata from meta/qb.yml or from executing meta/qb and parsing the YAML written to stdout.
if cache is true caches it as @meta
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/qb/role.rb', line 551 def cache = true = if @meta_path.extname == '.yml' contents = begin @meta_path.read rescue Exception => error raise QB::Role::MetadataError, "Failed to read metadata file at #{ @meta_path.to_s }, " + "error: #{ error.inspect }" end begin YAML.load(contents) || {} rescue Exception => error raise QB::Role::MetadataError, "Failed to load metadata YAML from #{ @meta_path.to_s }, " + "error: #{ error.inspect }" end else YAML.load(Cmds.out!(@meta_path.realpath.to_s)) || {} end if cache @meta = end end |
#meta ⇒ Hash{String => Object}
Returns the QB metadata for the role.
582 583 584 |
# File 'lib/qb/role.rb', line 582 def @meta || end |
#mkdir ⇒ Object
if the exe should auto-make the directory. this is nice for most roles but some need it to be missing
657 658 659 |
# File 'lib/qb/role.rb', line 657 def mkdir !!('mkdir', true) end |
#namespace ⇒ Object
Instance Methods
526 527 528 529 530 531 532 533 534 535 536 |
# File 'lib/qb/role.rb', line 526 def namespace *namespace_segments, last = @name.split File::Separator namespace_segments << last.split('.').first if last.include?('.') if namespace_segments.empty? nil else File.join *namespace_segments end end |
#namespaceless ⇒ Object
538 539 540 |
# File 'lib/qb/role.rb', line 538 def namespaceless File.basename(@name).split('.', 2).last end |
#option_metas ⇒ Object
get the options from the metadata, defaulting to [] if none defined
599 600 601 |
# File 'lib/qb/role.rb', line 599 def ['options', 'opts', 'vars'], [] end |
#options(include_path = []) ⇒ Array<QB::Options::Option> an array of Option for the role, including any included roles.
Returns ArrayQB::Options::Option an array of Option for the role, including any included roles.
607 608 609 610 611 612 613 614 615 616 617 |
# File 'lib/qb/role.rb', line 607 def include_path = [] .map {|| if .key? 'include' role_name = ['include'] role = QB::Role.require role_name role. QB::Role.get_include_path(role, , include_path) else QB::Options::Option.new self, , include_path end }.flatten end |
#options_key ⇒ Object
542 543 544 |
# File 'lib/qb/role.rb', line 542 def @display_path.to_s end |
#puts_examples ⇒ Object
examples text
750 751 752 753 754 |
# File 'lib/qb/role.rb', line 750 def puts_examples return unless examples puts "\n" + format_examples + "\n" end |
#qb_requirement ⇒ Gem::Requirement?
Get the Gem::Requirement parse of the qb_requirement key in
#meta (if it is defined), which specifies the required version of
qb for the role.
881 882 883 884 885 886 887 |
# File 'lib/qb/role.rb', line 881 def qb_requirement if ['requirements'] && ['requirements']['gems'] && ['requirements']['gems']['qb'] Gem::Requirement.new ['requirements']['gems']['qb'] end end |
#save_options ⇒ Object
651 652 653 |
# File 'lib/qb/role.rb', line 651 def !!('save_options', true) end |
#to_s ⇒ String
Returns #display_path.
908 909 910 |
# File 'lib/qb/role.rb', line 908 def to_s @display_path.to_s end |
#usage ⇒ String
Returns usage information formatted as plain text for the CLI.
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 |
# File 'lib/qb/role.rb', line 664 def usage # Split up options by required and optional. = [] = [] .each { |option| if option.required? << option else << option end } parts = ['qb [run]', name] .each { |option| parts << option.usage } unless .empty? parts << '[OPTIONS]' end if uses_default_dir? parts << 'DIRECTORY' end parts.join ' ' end |
#uses_default_dir? ⇒ Boolean
Returns @todo Document return value.
771 772 773 |
# File 'lib/qb/role.rb', line 771 def uses_default_dir? ['default_dir'] != false end |
#var_prefix ⇒ Object
gets the variable prefix that will be appended to cli options before
passing them to the role. defaults to #namespaceless unless specified
in meta.
590 591 592 593 594 595 |
# File 'lib/qb/role.rb', line 590 def var_prefix # ugh, i was generating meta/qb.yml files that set 'var_prefix' to # `null`, but it would be nice to # 'var_prefix', namespaceless end |