cult 0.1.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +9 -0
 - data/Gemfile +4 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +240 -0
 - data/Rakefile +6 -0
 - data/cult +1 -0
 - data/cult.gemspec +38 -0
 - data/doc/welcome.txt +1 -0
 - data/exe/cult +86 -0
 - data/lib/cult/artifact.rb +45 -0
 - data/lib/cult/cli/common.rb +265 -0
 - data/lib/cult/cli/console_cmd.rb +124 -0
 - data/lib/cult/cli/cri_extensions.rb +84 -0
 - data/lib/cult/cli/init_cmd.rb +116 -0
 - data/lib/cult/cli/load.rb +26 -0
 - data/lib/cult/cli/node_cmd.rb +205 -0
 - data/lib/cult/cli/provider_cmd.rb +123 -0
 - data/lib/cult/cli/role_cmd.rb +149 -0
 - data/lib/cult/cli/task_cmd.rb +140 -0
 - data/lib/cult/commander.rb +103 -0
 - data/lib/cult/config.rb +22 -0
 - data/lib/cult/definition.rb +112 -0
 - data/lib/cult/driver.rb +88 -0
 - data/lib/cult/drivers/common.rb +192 -0
 - data/lib/cult/drivers/digital_ocean_driver.rb +179 -0
 - data/lib/cult/drivers/linode_driver.rb +282 -0
 - data/lib/cult/drivers/load.rb +26 -0
 - data/lib/cult/drivers/script_driver.rb +27 -0
 - data/lib/cult/drivers/vultr_driver.rb +217 -0
 - data/lib/cult/named_array.rb +129 -0
 - data/lib/cult/node.rb +62 -0
 - data/lib/cult/project.rb +169 -0
 - data/lib/cult/provider.rb +134 -0
 - data/lib/cult/role.rb +213 -0
 - data/lib/cult/skel.rb +85 -0
 - data/lib/cult/task.rb +64 -0
 - data/lib/cult/template.rb +92 -0
 - data/lib/cult/transferable.rb +61 -0
 - data/lib/cult/version.rb +3 -0
 - data/lib/cult.rb +4 -0
 - data/skel/.cultconsolerc +4 -0
 - data/skel/.cultrc.erb +29 -0
 - data/skel/README.md.erb +22 -0
 - data/skel/keys/.keep +0 -0
 - data/skel/nodes/.keep +0 -0
 - data/skel/providers/.keep +0 -0
 - data/skel/roles/all/role.json +4 -0
 - data/skel/roles/all/tasks/00000-do-something-cool +27 -0
 - data/skel/roles/bootstrap/files/cult-motd +45 -0
 - data/skel/roles/bootstrap/role.json +4 -0
 - data/skel/roles/bootstrap/tasks/00000-set-hostname +22 -0
 - data/skel/roles/bootstrap/tasks/00001-add-cult-user +21 -0
 - data/skel/roles/bootstrap/tasks/00002-install-cult-motd +9 -0
 - metadata +183 -0
 
| 
         @@ -0,0 +1,265 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'io/console'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'shellwords'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Cult
         
     | 
| 
      
 5 
     | 
    
         
            +
              module CLI
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                class CLIError < RuntimeError
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                module_function
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # This sets the global project based on a directory
         
     | 
| 
      
 13 
     | 
    
         
            +
                def set_project(path)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  Cult.project = Cult::Project.locate(path)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  if Cult.project.nil?
         
     | 
| 
      
 16 
     | 
    
         
            +
                    fail CLIError, "#{path} does not contain a valid Cult project"
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                # Quiet mode controls how verbose `say` is
         
     | 
| 
      
 22 
     | 
    
         
            +
                def quiet=(v)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @quiet = v
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def quiet?
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @quiet
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def say(v)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  puts v unless @quiet
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                # yes=true automatically answers yes to "yes_no" questions.
         
     | 
| 
      
 38 
     | 
    
         
            +
                def yes=(v)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @yes = v
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def yes?
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @yes
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                # Asks a yes or no question with promp.  The prompt defaults to "Yes".  If
         
     | 
| 
      
 49 
     | 
    
         
            +
                # Cli.yes=true, true is returned without showing the prompt.
         
     | 
| 
      
 50 
     | 
    
         
            +
                def yes_no?(prompt, default: true)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  return true if yes?
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  default = case default
         
     | 
| 
      
 54 
     | 
    
         
            +
                    when :y, :yes
         
     | 
| 
      
 55 
     | 
    
         
            +
                      true
         
     | 
| 
      
 56 
     | 
    
         
            +
                    when :n, :no
         
     | 
| 
      
 57 
     | 
    
         
            +
                      false
         
     | 
| 
      
 58 
     | 
    
         
            +
                    when true, false
         
     | 
| 
      
 59 
     | 
    
         
            +
                      default
         
     | 
| 
      
 60 
     | 
    
         
            +
                    else
         
     | 
| 
      
 61 
     | 
    
         
            +
                      fail ArgumentError, "invalid :default"
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  loop do
         
     | 
| 
      
 65 
     | 
    
         
            +
                    y =  default ? Rainbow('Y').bright : Rainbow('y').darkgray
         
     | 
| 
      
 66 
     | 
    
         
            +
                    n = !default ? Rainbow('N').bright : Rainbow('n').darkgray
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 69 
     | 
    
         
            +
                      print "#{prompt} #{y}/#{n}: "
         
     | 
| 
      
 70 
     | 
    
         
            +
                      case $stdin.gets.chomp
         
     | 
| 
      
 71 
     | 
    
         
            +
                        when ''
         
     | 
| 
      
 72 
     | 
    
         
            +
                          return default
         
     | 
| 
      
 73 
     | 
    
         
            +
                        when /^[Yy]/
         
     | 
| 
      
 74 
     | 
    
         
            +
                          return true
         
     | 
| 
      
 75 
     | 
    
         
            +
                        when /^[Nn]/
         
     | 
| 
      
 76 
     | 
    
         
            +
                          return false
         
     | 
| 
      
 77 
     | 
    
         
            +
                        else
         
     | 
| 
      
 78 
     | 
    
         
            +
                          $stderr.puts "Unrecognized response"
         
     | 
| 
      
 79 
     | 
    
         
            +
                      end
         
     | 
| 
      
 80 
     | 
    
         
            +
                    rescue Interrupt
         
     | 
| 
      
 81 
     | 
    
         
            +
                      puts
         
     | 
| 
      
 82 
     | 
    
         
            +
                      raise
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                # Asks the user a question, and returns the response.  Ensures a newline
         
     | 
| 
      
 89 
     | 
    
         
            +
                # exists after the response.
         
     | 
| 
      
 90 
     | 
    
         
            +
                def ask(prompt)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  print "#{prompt}: "
         
     | 
| 
      
 92 
     | 
    
         
            +
                  $stdin.gets.chomp
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def prompt(*args)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  ask(*args)
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                # Disables echo to ask the user a password.
         
     | 
| 
      
 102 
     | 
    
         
            +
                def password(prompt)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  STDIN.noecho do
         
     | 
| 
      
 104 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 105 
     | 
    
         
            +
                      ask(prompt)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 107 
     | 
    
         
            +
                      puts
         
     | 
| 
      
 108 
     | 
    
         
            +
                    end
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                # it's common for drivers to need the user to visit a URL to
         
     | 
| 
      
 114 
     | 
    
         
            +
                # confirm an API key or similar.  This does this in the most
         
     | 
| 
      
 115 
     | 
    
         
            +
                # compatable way I know.
         
     | 
| 
      
 116 
     | 
    
         
            +
                def launch_browser(url)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  case RUBY_PLATFORM
         
     | 
| 
      
 118 
     | 
    
         
            +
                    when /darwin/
         
     | 
| 
      
 119 
     | 
    
         
            +
                      system "open", url
         
     | 
| 
      
 120 
     | 
    
         
            +
                    when /mswin|mingw|cygwin/
         
     | 
| 
      
 121 
     | 
    
         
            +
                      system "start", url
         
     | 
| 
      
 122 
     | 
    
         
            +
                    else
         
     | 
| 
      
 123 
     | 
    
         
            +
                      system "xdg-open", url
         
     | 
| 
      
 124 
     | 
    
         
            +
                  end
         
     | 
| 
      
 125 
     | 
    
         
            +
                end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                # We actually want "base 47", so we have to generate substantially more
         
     | 
| 
      
 129 
     | 
    
         
            +
                # characters than len.  The method already generates 1.25*len characters,
         
     | 
| 
      
 130 
     | 
    
         
            +
                # but is offset by _ and - that we discard.  With the other characters we
         
     | 
| 
      
 131 
     | 
    
         
            +
                # discard, we usethe minimum multiplier which makes a retry "rare" (every
         
     | 
| 
      
 132 
     | 
    
         
            +
                # few thousand ids at 6 len), then handle that case.
         
     | 
| 
      
 133 
     | 
    
         
            +
                def unique_id(len = 8)
         
     | 
| 
      
 134 
     | 
    
         
            +
                  @uniq_id_disallowed ||= /[^abcdefhjkmnpqrtvwxyzABCDEFGHJKMNPQRTVWXY2346789]/
         
     | 
| 
      
 135 
     | 
    
         
            +
                  candidate = SecureRandom.urlsafe_base64((len * 2.1).ceil)
         
     | 
| 
      
 136 
     | 
    
         
            +
                                          .gsub(@uniq_id_disallowed, '')
         
     | 
| 
      
 137 
     | 
    
         
            +
                  fail RangeError if candidate.size < len
         
     | 
| 
      
 138 
     | 
    
         
            +
                  candidate[0...len]
         
     | 
| 
      
 139 
     | 
    
         
            +
                rescue RangeError
         
     | 
| 
      
 140 
     | 
    
         
            +
                  retry
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                # v is an option or argv value from a user, label: is the name of it.
         
     | 
| 
      
 145 
     | 
    
         
            +
                #
         
     | 
| 
      
 146 
     | 
    
         
            +
                # This asserts that `v` is in the collection `from`, and returns it.
         
     | 
| 
      
 147 
     | 
    
         
            +
                # if `exist` is false, it verifies that v is NOT in the collection and
         
     | 
| 
      
 148 
     | 
    
         
            +
                # returns v.
         
     | 
| 
      
 149 
     | 
    
         
            +
                #
         
     | 
| 
      
 150 
     | 
    
         
            +
                # As a convenience, `from` can be a class like Role, which will imply
         
     | 
| 
      
 151 
     | 
    
         
            +
                # 'Cult.project.roles'
         
     | 
| 
      
 152 
     | 
    
         
            +
                #
         
     | 
| 
      
 153 
     | 
    
         
            +
                # CLIError is raised if these invariants are violated
         
     | 
| 
      
 154 
     | 
    
         
            +
                def fetch_item(v, from:, label: nil, exist: true, method: :fetch)
         
     | 
| 
      
 155 
     | 
    
         
            +
                  implied_from = case
         
     | 
| 
      
 156 
     | 
    
         
            +
                    when from == Driver;   Cult::Drivers.all
         
     | 
| 
      
 157 
     | 
    
         
            +
                    when from == Provider; Cult.project.providers
         
     | 
| 
      
 158 
     | 
    
         
            +
                    when from == Role;     Cult.project.roles
         
     | 
| 
      
 159 
     | 
    
         
            +
                    when from == Node;     Cult.project.nodes
         
     | 
| 
      
 160 
     | 
    
         
            +
                    else;                  nil
         
     | 
| 
      
 161 
     | 
    
         
            +
                  end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                  label ||= implied_from ? from.name.split('::')[-1].downcase : nil
         
     | 
| 
      
 164 
     | 
    
         
            +
                  from = implied_from
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                  fail ArgumentError, "label cannot be implied" if label.nil?
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                  unless [:fetch, :all].include?(method)
         
     | 
| 
      
 169 
     | 
    
         
            +
                    fail ArgumentError, "method must be :fetch or :all"
         
     | 
| 
      
 170 
     | 
    
         
            +
                  end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                  # We got no argument
         
     | 
| 
      
 173 
     | 
    
         
            +
                  fail CLIError, "Expected #{label}" if v.nil?
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                  if exist
         
     | 
| 
      
 176 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 177 
     | 
    
         
            +
                      from.send(method, v).tap do |r|
         
     | 
| 
      
 178 
     | 
    
         
            +
                        # Make sure
         
     | 
| 
      
 179 
     | 
    
         
            +
                        fail KeyError if method == :all && r.empty?
         
     | 
| 
      
 180 
     | 
    
         
            +
                      end
         
     | 
| 
      
 181 
     | 
    
         
            +
                    rescue KeyError
         
     | 
| 
      
 182 
     | 
    
         
            +
                      fail CLIError, "#{label} does not exist: #{v}"
         
     | 
| 
      
 183 
     | 
    
         
            +
                    end
         
     | 
| 
      
 184 
     | 
    
         
            +
                  else
         
     | 
| 
      
 185 
     | 
    
         
            +
                    if from.key?(v)
         
     | 
| 
      
 186 
     | 
    
         
            +
                      fail CLIError, "#{label} already exists: #{v}"
         
     | 
| 
      
 187 
     | 
    
         
            +
                    end
         
     | 
| 
      
 188 
     | 
    
         
            +
                    v
         
     | 
| 
      
 189 
     | 
    
         
            +
                  end
         
     | 
| 
      
 190 
     | 
    
         
            +
                end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                # Takes a list of keys and returns an array of objects that correspond
         
     | 
| 
      
 194 
     | 
    
         
            +
                # to any of them.  If required is true, each key must correspond to at
         
     | 
| 
      
 195 
     | 
    
         
            +
                # least one object.
         
     | 
| 
      
 196 
     | 
    
         
            +
                def fetch_items(*keys, **kw)
         
     | 
| 
      
 197 
     | 
    
         
            +
                  keys.flatten.map do |key|
         
     | 
| 
      
 198 
     | 
    
         
            +
                    fetch_item(key, method: :all, **kw)
         
     | 
| 
      
 199 
     | 
    
         
            +
                  end.flatten
         
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                # This intercepts GemNeededError and does the installation dance.  It looks
         
     | 
| 
      
 204 
     | 
    
         
            +
                # a bit hairy because it has a few resumption points, e.g., attempts user
         
     | 
| 
      
 205 
     | 
    
         
            +
                # gem install, and if that fails, tries the sudo gem install.
         
     | 
| 
      
 206 
     | 
    
         
            +
                def offer_gem_install(&block)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  prompt_install = ->(gems) do
         
     | 
| 
      
 208 
     | 
    
         
            +
                    unless quiet?
         
     | 
| 
      
 209 
     | 
    
         
            +
                      print <<~EOD
         
     | 
| 
      
 210 
     | 
    
         
            +
                        This driver requires the installation of one or more gems:
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                          #{gems.inspect}
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                        Cult can install them for you.
         
     | 
| 
      
 215 
     | 
    
         
            +
                      EOD
         
     | 
| 
      
 216 
     | 
    
         
            +
                    end
         
     | 
| 
      
 217 
     | 
    
         
            +
                    yes_no?("Install?")
         
     | 
| 
      
 218 
     | 
    
         
            +
                  end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                  try_install = ->(gem, sudo: false) do
         
     | 
| 
      
 221 
     | 
    
         
            +
                    cmd = "gem install #{Shellwords.escape(gem)}"
         
     | 
| 
      
 222 
     | 
    
         
            +
                    cmd = "sudo #{cmd}" if sudo
         
     | 
| 
      
 223 
     | 
    
         
            +
                    puts "executing: #{cmd}"
         
     | 
| 
      
 224 
     | 
    
         
            +
                    system cmd
         
     | 
| 
      
 225 
     | 
    
         
            +
                    $?.success?
         
     | 
| 
      
 226 
     | 
    
         
            +
                  end
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 229 
     | 
    
         
            +
                    yield
         
     | 
| 
      
 230 
     | 
    
         
            +
                  rescue ::Cult::Driver::GemNeededError => needed
         
     | 
| 
      
 231 
     | 
    
         
            +
                    sudo = false
         
     | 
| 
      
 232 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 233 
     | 
    
         
            +
                      sudo = catch :sudo_attempt do
         
     | 
| 
      
 234 
     | 
    
         
            +
                        # We don't want to show this again on a retry
         
     | 
| 
      
 235 
     | 
    
         
            +
                        raise unless sudo || prompt_install.(needed.gems)
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                        needed.gems.each do |gem|
         
     | 
| 
      
 238 
     | 
    
         
            +
                          success = try_install.(gem, sudo: sudo)
         
     | 
| 
      
 239 
     | 
    
         
            +
                          if !success
         
     | 
| 
      
 240 
     | 
    
         
            +
                            if sudo
         
     | 
| 
      
 241 
     | 
    
         
            +
                              puts "Nothing seemed to have worked.  Giving up."
         
     | 
| 
      
 242 
     | 
    
         
            +
                              puts "The gems needed are #{needed.gems.inspect}."
         
     | 
| 
      
 243 
     | 
    
         
            +
                              raise
         
     | 
| 
      
 244 
     | 
    
         
            +
                            else
         
     | 
| 
      
 245 
     | 
    
         
            +
                              puts "It doesn't look like that went well."
         
     | 
| 
      
 246 
     | 
    
         
            +
                              if yes_no?("Retry with sudo?")
         
     | 
| 
      
 247 
     | 
    
         
            +
                                throw :sudo_attempt, true
         
     | 
| 
      
 248 
     | 
    
         
            +
                              end
         
     | 
| 
      
 249 
     | 
    
         
            +
                              raise
         
     | 
| 
      
 250 
     | 
    
         
            +
                            end
         
     | 
| 
      
 251 
     | 
    
         
            +
                          end
         
     | 
| 
      
 252 
     | 
    
         
            +
                        end
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                        # We exit our non-loop: Everything went fine.
         
     | 
| 
      
 255 
     | 
    
         
            +
                        break
         
     | 
| 
      
 256 
     | 
    
         
            +
                      end
         
     | 
| 
      
 257 
     | 
    
         
            +
                    end
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                    # Everything went fine, we need to retry the user-supplied block.
         
     | 
| 
      
 260 
     | 
    
         
            +
                    Gem.refresh
         
     | 
| 
      
 261 
     | 
    
         
            +
                    retry
         
     | 
| 
      
 262 
     | 
    
         
            +
                  end
         
     | 
| 
      
 263 
     | 
    
         
            +
                end
         
     | 
| 
      
 264 
     | 
    
         
            +
              end
         
     | 
| 
      
 265 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,124 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'delegate'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rainbow'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Cult
         
     | 
| 
      
 5 
     | 
    
         
            +
              module CLI
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                class ConsoleContext < SimpleDelegator
         
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :original_argv
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def initialize(project, argv)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    super(project)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                    @original_argv = [$0, *argv]
         
     | 
| 
      
 15 
     | 
    
         
            +
                    ENV['CULT_PROJECT'] = self.path
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def load_rc
         
     | 
| 
      
 20 
     | 
    
         
            +
                    consolerc = project.location_of(".cultconsolerc")
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    # We don't `load' so the rc file has a more convenient context.
         
     | 
| 
      
 23 
     | 
    
         
            +
                    eval File.read(consolerc) if File.exist?(consolerc)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  private def exit(*)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    # IRB tries to alias this. And it must be private, or it warns.  WTF.
         
     | 
| 
      
 29 
     | 
    
         
            +
                    super
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # Gives us an escape hatch to get the real, non-decorated object
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def project
         
     | 
| 
      
 35 
     | 
    
         
            +
                    __getobj__
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def cult(*argv)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    system $0, *argv
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def binding
         
     | 
| 
      
 45 
     | 
    
         
            +
                    super
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                module_function
         
     | 
| 
      
 50 
     | 
    
         
            +
                def console_cmd
         
     | 
| 
      
 51 
     | 
    
         
            +
                  Cri::Command.define do
         
     | 
| 
      
 52 
     | 
    
         
            +
                    name        'console'
         
     | 
| 
      
 53 
     | 
    
         
            +
                    summary     'Launch an REPL with you project loaded'
         
     | 
| 
      
 54 
     | 
    
         
            +
                    description <<~EOD.format_description
         
     | 
| 
      
 55 
     | 
    
         
            +
                      The Cult console loads your project, and starts a Ruby REPL.  This can
         
     | 
| 
      
 56 
     | 
    
         
            +
                      be useful for troubleshooting, or just poking around the project.
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                      A few convenience global variables are set to inspect.
         
     | 
| 
      
 59 
     | 
    
         
            +
                    EOD
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    flag :i,  :irb,    'IRB (default)'
         
     | 
| 
      
 62 
     | 
    
         
            +
                    flag :r,  :ripl,   'Ripl'
         
     | 
| 
      
 63 
     | 
    
         
            +
                    flag :p,  :pry,    'Pry'
         
     | 
| 
      
 64 
     | 
    
         
            +
                    flag nil, :reexec, 'Console has been exec\'d for a reload'
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    run(arguments: 0) do |opts, args, cmd|
         
     | 
| 
      
 67 
     | 
    
         
            +
                      context = ConsoleContext.new(Cult.project, ARGV)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                      if opts[:reexec]
         
     | 
| 
      
 70 
     | 
    
         
            +
                        $stderr.puts "Reloaded."
         
     | 
| 
      
 71 
     | 
    
         
            +
                      else
         
     | 
| 
      
 72 
     | 
    
         
            +
                        $stderr.puts <<~EOD
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                          Welcome to the #{Rainbow('Cult').green} Console.
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                          Your project has been made accessible via 'project', and forwards
         
     | 
| 
      
 77 
     | 
    
         
            +
                          via 'self':
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                            => #{context.inspect}
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                          Useful methods: nodes, roles, providers
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                        EOD
         
     | 
| 
      
 84 
     | 
    
         
            +
                      end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                      context.load_rc
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                      if opts[:ripl]
         
     | 
| 
      
 89 
     | 
    
         
            +
                        require 'ripl'
         
     | 
| 
      
 90 
     | 
    
         
            +
                        ARGV.clear
         
     | 
| 
      
 91 
     | 
    
         
            +
                        # Look, something reasonable:
         
     | 
| 
      
 92 
     | 
    
         
            +
                        Ripl.start(binding: context.binding)
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                      elsif opts[:pry]
         
     | 
| 
      
 95 
     | 
    
         
            +
                        require 'pry'
         
     | 
| 
      
 96 
     | 
    
         
            +
                        context.binding.pry
         
     | 
| 
      
 97 
     | 
    
         
            +
                      else
         
     | 
| 
      
 98 
     | 
    
         
            +
                        # irb: This is ridiculous.
         
     | 
| 
      
 99 
     | 
    
         
            +
                        require 'irb'
         
     | 
| 
      
 100 
     | 
    
         
            +
                        ARGV.clear
         
     | 
| 
      
 101 
     | 
    
         
            +
                        IRB.setup(nil)
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                        irb = IRB::Irb.new(IRB::WorkSpace.new(context.binding))
         
     | 
| 
      
 104 
     | 
    
         
            +
                        IRB.conf[:MAIN_CONTEXT] = irb.context
         
     | 
| 
      
 105 
     | 
    
         
            +
                        IRB.conf[:IRB_RC].call(irb.context) if IRB.conf[:IRB_RC]
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                        trap("SIGINT") do
         
     | 
| 
      
 108 
     | 
    
         
            +
                          irb.signal_handle
         
     | 
| 
      
 109 
     | 
    
         
            +
                        end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 112 
     | 
    
         
            +
                          catch(:IRB_EXIT) do
         
     | 
| 
      
 113 
     | 
    
         
            +
                            irb.eval_input
         
     | 
| 
      
 114 
     | 
    
         
            +
                          end
         
     | 
| 
      
 115 
     | 
    
         
            +
                        ensure
         
     | 
| 
      
 116 
     | 
    
         
            +
                          IRB::irb_at_exit
         
     | 
| 
      
 117 
     | 
    
         
            +
                        end
         
     | 
| 
      
 118 
     | 
    
         
            +
                      end
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
                end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,84 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'cri'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Cult
         
     | 
| 
      
 4 
     | 
    
         
            +
              module CLI
         
     | 
| 
      
 5 
     | 
    
         
            +
                class ::String
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def format_description
         
     | 
| 
      
 7 
     | 
    
         
            +
                    self.gsub(/(\S)\n(\S)/m, '\1 \2')
         
     | 
| 
      
 8 
     | 
    
         
            +
                        .gsub(/\.[ ]{2}(\S)/m, '. \1')
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                module CommandExtensions
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def project_required?
         
     | 
| 
      
 15 
     | 
    
         
            +
                    defined?(@project_required) ? @project_required : true
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def project_required=(v)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @project_required = v
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  attr_accessor :argument_spec
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  # This function returns a wrapped version of the block passed to `run`
         
     | 
| 
      
 28 
     | 
    
         
            +
                  def block
         
     | 
| 
      
 29 
     | 
    
         
            +
                    lambda do |opts, args, cmd|
         
     | 
| 
      
 30 
     | 
    
         
            +
                      if project_required? && Cult.project.nil?
         
     | 
| 
      
 31 
     | 
    
         
            +
                        fail CLIError, "command '#{name}' requires a Cult project"
         
     | 
| 
      
 32 
     | 
    
         
            +
                      end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                      check_argument_spec!(args, argument_spec) if argument_spec
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      super.call(opts, args, cmd)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def check_argument_spec!(args, range)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    range = (range..range) if range.is_a?(Integer)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    if range.end == -1
         
     | 
| 
      
 44 
     | 
    
         
            +
                      range = range.begin .. Float::INFINITY
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    unless range.cover?(args.size)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      msg = case
         
     | 
| 
      
 49 
     | 
    
         
            +
                        when range.size == 1 && range.begin == 0
         
     | 
| 
      
 50 
     | 
    
         
            +
                          "accepts no arguments"
         
     | 
| 
      
 51 
     | 
    
         
            +
                        when range.size == 1 && range.begin == 1
         
     | 
| 
      
 52 
     | 
    
         
            +
                          "accepts one argument"
         
     | 
| 
      
 53 
     | 
    
         
            +
                        when range.begin == range.end
         
     | 
| 
      
 54 
     | 
    
         
            +
                          "accepts exactly #{range.begin} arguments"
         
     | 
| 
      
 55 
     | 
    
         
            +
                        else
         
     | 
| 
      
 56 
     | 
    
         
            +
                          if range.end == Float::INFINITY
         
     | 
| 
      
 57 
     | 
    
         
            +
                            "requires #{range.begin}+ arguments"
         
     | 
| 
      
 58 
     | 
    
         
            +
                          else
         
     | 
| 
      
 59 
     | 
    
         
            +
                            "accepts #{range} arguments"
         
     | 
| 
      
 60 
     | 
    
         
            +
                          end
         
     | 
| 
      
 61 
     | 
    
         
            +
                      end
         
     | 
| 
      
 62 
     | 
    
         
            +
                      fail CLIError, "Command #{msg}"
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  Cri::Command.prepend(self)
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                module CommandDSLExtensions
         
     | 
| 
      
 71 
     | 
    
         
            +
                  def optional_project
         
     | 
| 
      
 72 
     | 
    
         
            +
                    @command.project_required = false
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  def run(arguments: nil, &block)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    @command.argument_spec = arguments if arguments
         
     | 
| 
      
 78 
     | 
    
         
            +
                    super(&block)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  Cri::CommandDSL.prepend(self)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
              end
         
     | 
| 
      
 84 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,116 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'cult/skel'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'cult/project'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'cult/drivers/load'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Cult
         
     | 
| 
      
 6 
     | 
    
         
            +
              module CLI
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                module_function
         
     | 
| 
      
 9 
     | 
    
         
            +
                def init_cmd
         
     | 
| 
      
 10 
     | 
    
         
            +
                  Cri::Command.define do
         
     | 
| 
      
 11 
     | 
    
         
            +
                    drivers = Cult::Drivers.all.map{|d| d.driver_name }.join ", "
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    optional_project
         
     | 
| 
      
 14 
     | 
    
         
            +
                    name        'init'
         
     | 
| 
      
 15 
     | 
    
         
            +
                    aliases     'new'
         
     | 
| 
      
 16 
     | 
    
         
            +
                    usage       'init DIRECTORY'
         
     | 
| 
      
 17 
     | 
    
         
            +
                    summary     'Create a new Cult project'
         
     | 
| 
      
 18 
     | 
    
         
            +
                    description <<~EOD.format_description
         
     | 
| 
      
 19 
     | 
    
         
            +
                      Generates a new Cult project, based on a project skeleton.
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      The most useful option is --driver, which both specifies a driver and
         
     | 
| 
      
 22 
     | 
    
         
            +
                      sets up a provider of the same name.  This will make sure the
         
     | 
| 
      
 23 
     | 
    
         
            +
                      dependencies for using the driver are install, and any bookkeeping
         
     | 
| 
      
 24 
     | 
    
         
            +
                      required to start interacting with your VPS provider is handled.
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                      This usually involves entering an account name or getting an API key.
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                      The default provider is "script", which isn't too pleasant, but has
         
     | 
| 
      
 29 
     | 
    
         
            +
                      no dependencies.  The "script" driver manages your fleet by executing
         
     | 
| 
      
 30 
     | 
    
         
            +
                      scripts in $CULT_PROJECT/script, which you have to implement.  This is
         
     | 
| 
      
 31 
     | 
    
         
            +
                      tedious, but very doable.  However, if Cult knows about your provider,
         
     | 
| 
      
 32 
     | 
    
         
            +
                      it can handle all of this without you having to do anything.
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                      Cult knows about the following providers:
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      > #{drivers}
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                      The init process just gets you started, and it's nothing that couldn't
         
     | 
| 
      
 39 
     | 
    
         
            +
                      be accomplished by hand, so if you want to change anything later, it's
         
     | 
| 
      
 40 
     | 
    
         
            +
                      not a big deal.
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                      The project generated sets up a pretty common configuration: an 'all'
         
     | 
| 
      
 43 
     | 
    
         
            +
                      role, a 'bootstrap' role, and a demo task that puts a colorful banner
         
     | 
| 
      
 44 
     | 
    
         
            +
                      in each node's MOTD.
         
     | 
| 
      
 45 
     | 
    
         
            +
                    EOD
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    required :d, :driver,   'Driver with which to create your provider'
         
     | 
| 
      
 48 
     | 
    
         
            +
                    required :p, :provider, 'Specify an explicit provider name'
         
     | 
| 
      
 49 
     | 
    
         
            +
                    flag     :g, :git,      'Enable Git integration'
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    run(arguments: 1) do |opts, args, cmd|
         
     | 
| 
      
 52 
     | 
    
         
            +
                      project = Project.new(args[0])
         
     | 
| 
      
 53 
     | 
    
         
            +
                      if project.exist?
         
     | 
| 
      
 54 
     | 
    
         
            +
                        fail CLIError, "a Cult project already exists in #{project.path}"
         
     | 
| 
      
 55 
     | 
    
         
            +
                      end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                      project.git_integration = opts[:git]
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                      driver_cls = if !opts[:provider] && !opts[:driver]
         
     | 
| 
      
 60 
     | 
    
         
            +
                        opts[:provider] ||= 'scripts'
         
     | 
| 
      
 61 
     | 
    
         
            +
                        CLI.fetch_item(opts[:provider], from: Driver)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      elsif opts[:provider] && !opts[:driver]
         
     | 
| 
      
 63 
     | 
    
         
            +
                        CLI.fetch_item(opts[:provider], from: Driver)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      elsif opts[:driver] && !opts[:provider]
         
     | 
| 
      
 65 
     | 
    
         
            +
                        CLI.fetch_item(opts[:driver], from: Driver).tap do |dc|
         
     | 
| 
      
 66 
     | 
    
         
            +
                          opts[:provider] = dc.driver_name
         
     | 
| 
      
 67 
     | 
    
         
            +
                        end
         
     | 
| 
      
 68 
     | 
    
         
            +
                      elsif opts[:driver]
         
     | 
| 
      
 69 
     | 
    
         
            +
                        CLI.fetch_item(opts[:driver], from: Driver)
         
     | 
| 
      
 70 
     | 
    
         
            +
                      end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                      fail CLIError, "Hmm, no driver class" if driver_cls.nil?
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                      skel = Skel.new(project)
         
     | 
| 
      
 75 
     | 
    
         
            +
                      skel.copy!
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                      provider_conf = {
         
     | 
| 
      
 78 
     | 
    
         
            +
                        name: opts[:provider],
         
     | 
| 
      
 79 
     | 
    
         
            +
                        driver: driver_cls.driver_name
         
     | 
| 
      
 80 
     | 
    
         
            +
                      }
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                      CLI.offer_gem_install do
         
     | 
| 
      
 83 
     | 
    
         
            +
                        driver_conf = driver_cls.setup!
         
     | 
| 
      
 84 
     | 
    
         
            +
                        provider_conf.merge!(driver_conf)
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                        provider_dir = File.join(project.location_of("providers"),
         
     | 
| 
      
 88 
     | 
    
         
            +
                                                 provider_conf[:name])
         
     | 
| 
      
 89 
     | 
    
         
            +
                        FileUtils.mkdir_p(provider_dir)
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                        provider_file = File.join(provider_dir,
         
     | 
| 
      
 93 
     | 
    
         
            +
                                                  project.dump_name("provider"))
         
     | 
| 
      
 94 
     | 
    
         
            +
                        File.write(provider_file, project.dump_object(provider_conf))
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                        defaults_file = File.join(provider_dir,
         
     | 
| 
      
 98 
     | 
    
         
            +
                                                  project.dump_name("defaults"))
         
     | 
| 
      
 99 
     | 
    
         
            +
                        defaults = Provider.generate_defaults(provider_conf)
         
     | 
| 
      
 100 
     | 
    
         
            +
                        File.write(defaults_file, project.dump_object(defaults))
         
     | 
| 
      
 101 
     | 
    
         
            +
                      end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                      if opts[:git]
         
     | 
| 
      
 104 
     | 
    
         
            +
                        Dir.chdir(project.path) do
         
     | 
| 
      
 105 
     | 
    
         
            +
                          `git init .`
         
     | 
| 
      
 106 
     | 
    
         
            +
                          `git add -A`
         
     | 
| 
      
 107 
     | 
    
         
            +
                          `git commit -m "[Cult] Created new project"`
         
     | 
| 
      
 108 
     | 
    
         
            +
                        end
         
     | 
| 
      
 109 
     | 
    
         
            +
                      end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
              end
         
     | 
| 
      
 116 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'cri'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'cult/cli/cri_extensions'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Cult
         
     | 
| 
      
 5 
     | 
    
         
            +
              module CLI
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                module_function
         
     | 
| 
      
 8 
     | 
    
         
            +
                def load_commands!
         
     | 
| 
      
 9 
     | 
    
         
            +
                  Dir.glob(File.join(__dir__, "*_cmd.rb")).each do |file|
         
     | 
| 
      
 10 
     | 
    
         
            +
                    require file
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def commands
         
     | 
| 
      
 16 
     | 
    
         
            +
                  Cult::CLI.methods(false).select do |m|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    m.to_s.match(/_cmd\z/)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end.map do |m|
         
     | 
| 
      
 19 
     | 
    
         
            +
                    Cult::CLI.send(m)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            Cult::CLI.load_commands!
         
     |