stemcell 0.0.11 → 0.2.0
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.
- data/README.md +75 -11
- data/bin/stemcell +47 -14
- data/examples/stemcellrc +10 -0
- data/lib/stemcell/templates/bootstrap.sh.erb +80 -66
- data/lib/stemcell/version.rb +1 -1
- data/lib/stemcell.rb +81 -53
- metadata +3 -2
    
        data/README.md
    CHANGED
    
    | @@ -1,28 +1,92 @@ | |
| 1 | 
            -
            # Stemcell
         | 
| 1 | 
            +
            # Stemcell #
         | 
| 2 2 |  | 
| 3 | 
            -
            Stemcell launches instances
         | 
| 3 | 
            +
            Stemcell launches instances in EC2.
         | 
| 4 | 
            +
            These instances are created to your specification, with knobs like AMI, instance type, and region exposed.
         | 
| 5 | 
            +
            The instances are bootstrapped with chef-solo, using a specified git repo and branch as the source of roles and recipes.
         | 
| 4 6 |  | 
| 5 7 | 
             
            ## Installation
         | 
| 6 8 |  | 
| 7 9 | 
             
            Add this line to your application's Gemfile:
         | 
| 8 10 |  | 
| 9 | 
            -
             | 
| 11 | 
            +
            ```bash
         | 
| 12 | 
            +
            gem 'stemcell'
         | 
| 13 | 
            +
            ```
         | 
| 10 14 |  | 
| 11 15 | 
             
            And then execute:
         | 
| 12 16 |  | 
| 13 | 
            -
             | 
| 17 | 
            +
            ```bash
         | 
| 18 | 
            +
            $ bundle
         | 
| 19 | 
            +
            ```
         | 
| 14 20 |  | 
| 15 21 | 
             
            Or install it yourself as:
         | 
| 16 22 |  | 
| 17 | 
            -
             | 
| 23 | 
            +
            ```bash
         | 
| 24 | 
            +
            $ gem install stemcell
         | 
| 25 | 
            +
            ```
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## Configuration
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            You should create an rc file for stemcell with your standard options
         | 
| 30 | 
            +
            (and place it in the root dir as .stemcellrc?). You can see an example
         | 
| 31 | 
            +
            in examples/stemcellrc. You can get most of the options from your
         | 
| 32 | 
            +
            .chef/knife.rb but you will need to get the new chef deploy key so
         | 
| 33 | 
            +
            that instances that you launch can download code.
         | 
| 18 34 |  | 
| 19 35 | 
             
            ## Usage
         | 
| 20 36 |  | 
| 37 | 
            +
            ### Include your base config:
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            ```bash
         | 
| 40 | 
            +
            $ source .stemcellrc
         | 
| 41 | 
            +
            ```
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            ### Simple launch:
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            ```bash
         | 
| 46 | 
            +
            $ ./bin/stemcell --chef-role $your_chef_role --git-branch $your_chef_branch
         | 
| 47 | 
            +
            ```
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            This will cause instance(s) to be launched and their ip's and instance
         | 
| 50 | 
            +
            id to be printed to the screen.
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ### More options:
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ```bash
         | 
| 55 | 
            +
            $ ./bin/stemcell --help
         | 
| 56 | 
            +
            ```
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            ### Watching install:
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            ```bash
         | 
| 61 | 
            +
            $ ssh unbutu@$IP 'tail -f /var/log/init*'
         | 
| 62 | 
            +
            ```
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            ### Terminating:
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            This still needs to be completed. For now, you can kill using the
         | 
| 67 | 
            +
            amazon cli tools or the web ui.
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            ## Automation ##
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            This README presents `stemcell` as a tool for administrators to use to create instances.
         | 
| 72 | 
            +
            However, we designed `stemcell` to be easily useful for automated systems which control server infrastructure.
         | 
| 73 | 
            +
            These automated systems can call out to `stemcell` on the command-line or use the ruby classes directly.
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            To see how we manage our cloud, check out the rest of SmartStack, especially Cortex, our auto-scaling system.
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            ## Similar Tools ##
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            There are a few additional tools which bootstrap EC2 instances with chef-solo.
         | 
| 80 | 
            +
            If you're using chef-server, obvious answer is [knife-ec2](https://github.com/opscode/knife-ec2).
         | 
| 81 | 
            +
            Unless you're working on a big team where lots of people edit cookbooks simultaneously, we strongly recommend this approach!
         | 
| 82 | 
            +
            It's especially excellent when paired with [hosted chef](http://www.opscode.com/hosted-chef/), which makes getting off the ground with configuration management fast and easy.
         | 
| 21 83 |  | 
| 22 | 
            -
             | 
| 84 | 
            +
            If you want to use knife-ec2 with chef-solo, you could use [knife solo](http://matschaffer.github.com/knife-solo/).
         | 
| 85 | 
            +
            Another approach which is great for interactive usage involves [using fabric to bootstrap chef](http://unfoldthat.com/2012/06/02/quick-deploy-chef-solo-fabric.html)([with gist](https://gist.github.com/va1en0k/2859812)).
         | 
| 23 86 |  | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 87 | 
            +
            Finally, we couldn't resist doing a bit of code archeology.
         | 
| 88 | 
            +
            People have been using chef with EC2 for a long time!
         | 
| 89 | 
            +
            One early article is [this one](http://web.archive.org/web/20110404114025/http://probablyinteractive.com/2009/3/29/Amazon%20EC2%20+%20Chef%20=%20Mmmmm.html), which isn't even on the web anymore.
         | 
| 90 | 
            +
            However, it's spawned some recently-active tools like [this](https://github.com/conormullen/chef-bootstrap) and [this](https://github.com/grempe/chef-solo-bootstrap).
         | 
| 91 | 
            +
            Similar approaches are mentioned [here](http://www.opinionatedprogrammer.com/2011/06/chef-solo-tutorial-managing-a-single-server-with-chef/), with code [herh](https://github.com/ciastek/ubuntu-chef-solo) or [here](https://github.com/riywo/ubuntu-chef-solo) (with accompanying [blog post](http://weblog.riywo.com/post/35976125760))
         | 
| 92 | 
            +
            [This article](http://illuminatedcomputing.com/posts/2012/02/simple-chef-solo-tutorial/), also mentions many worthwhile predecessors.
         | 
    
        data/bin/stemcell
    CHANGED
    
    | @@ -30,22 +30,22 @@ END_OF_BANNER | |
| 30 30 | 
             
                  :default => ENV['REGION'] ? ENV['REGION'] : 'us-east-1',
         | 
| 31 31 | 
             
                  )
         | 
| 32 32 |  | 
| 33 | 
            -
              opt(' | 
| 34 | 
            -
                  ' | 
| 33 | 
            +
              opt('instance_type',
         | 
| 34 | 
            +
                  'machine type to launch',
         | 
| 35 35 | 
             
                  :type => String,
         | 
| 36 | 
            -
                  :default => ENV[' | 
| 36 | 
            +
                  :default => ENV['INSTANCE_TYPE'] ? ENV['INSTANCE_TYPE'] : 'm1.small',
         | 
| 37 37 | 
             
                  )
         | 
| 38 38 |  | 
| 39 | 
            -
              opt(' | 
| 39 | 
            +
              opt('image_id',
         | 
| 40 40 | 
             
                  'ami to use for launch',
         | 
| 41 41 | 
             
                  :type => String,
         | 
| 42 | 
            -
                  :default => ENV[' | 
| 42 | 
            +
                  :default => ENV['IMAGE_ID'] ? ENV['IMAGE_ID'] : 'ami-d726abbe',
         | 
| 43 43 | 
             
                  )
         | 
| 44 44 |  | 
| 45 | 
            -
              opt(' | 
| 46 | 
            -
                  'security  | 
| 45 | 
            +
              opt('security_groups',
         | 
| 46 | 
            +
                  'security groups to launch instance with',
         | 
| 47 47 | 
             
                  :type => String,
         | 
| 48 | 
            -
                  :default => ENV[' | 
| 48 | 
            +
                  :default => ENV['SECURITY_GROUPS'] ? ENV['SECURITY_GROUPS'] : 'default',
         | 
| 49 49 | 
             
                  )
         | 
| 50 50 |  | 
| 51 51 | 
             
              opt('availability-zone',
         | 
| @@ -55,7 +55,7 @@ END_OF_BANNER | |
| 55 55 | 
             
                  )
         | 
| 56 56 |  | 
| 57 57 | 
             
              opt('tags',
         | 
| 58 | 
            -
                  ' | 
| 58 | 
            +
                  'comma-separated list of key=value pairs to apply',
         | 
| 59 59 | 
             
                  :type => String,
         | 
| 60 60 | 
             
                  :default => ENV['TAGS'],
         | 
| 61 61 | 
             
                  )
         | 
| @@ -78,6 +78,12 @@ END_OF_BANNER | |
| 78 78 | 
             
                  :default => ENV['CHEF_ROLE'],
         | 
| 79 79 | 
             
                  )
         | 
| 80 80 |  | 
| 81 | 
            +
              opt('chef_environment',
         | 
| 82 | 
            +
                  'chef environment in which this instance will run',
         | 
| 83 | 
            +
                  :type => String,
         | 
| 84 | 
            +
                  :default => ENV['CHEF_ENVIRONMENT'],
         | 
| 85 | 
            +
                  )
         | 
| 86 | 
            +
             | 
| 81 87 | 
             
              opt('git_origin',
         | 
| 82 88 | 
             
                  'git origin to use',
         | 
| 83 89 | 
             
                  :type => String,
         | 
| @@ -87,7 +93,7 @@ END_OF_BANNER | |
| 87 93 | 
             
              opt('git_branch',
         | 
| 88 94 | 
             
                  'git branch to run off',
         | 
| 89 95 | 
             
                  :type => String,
         | 
| 90 | 
            -
                  :default => ENV['GIT_BRANCH'],
         | 
| 96 | 
            +
                  :default => ENV['GIT_BRANCH'] ? ENV['GIT_BRANCH'] : 'production',
         | 
| 91 97 | 
             
                  )
         | 
| 92 98 |  | 
| 93 99 | 
             
              opt('git_key',
         | 
| @@ -98,8 +104,8 @@ END_OF_BANNER | |
| 98 104 |  | 
| 99 105 | 
             
              opt('count',
         | 
| 100 106 | 
             
                  'number of instances to launch',
         | 
| 101 | 
            -
                  :type =>  | 
| 102 | 
            -
                  :default => ENV['COUNT'],
         | 
| 107 | 
            +
                  :type => Integer,
         | 
| 108 | 
            +
                  :default => ENV['COUNT'] ? ENV['COUNT'] : 1,
         | 
| 103 109 | 
             
                  )
         | 
| 104 110 |  | 
| 105 111 | 
             
            end
         | 
| @@ -108,6 +114,8 @@ required_parameters = [ | |
| 108 114 | 
             
                                   'aws_access_key',
         | 
| 109 115 | 
             
                                   'aws_secret_key',
         | 
| 110 116 | 
             
                                   'chef_role',
         | 
| 117 | 
            +
                                   'chef_environment',
         | 
| 118 | 
            +
                                   'chef_data_bag_secret',
         | 
| 111 119 | 
             
                                   'git_branch',
         | 
| 112 120 | 
             
                                   'git_key',
         | 
| 113 121 | 
             
                                   'git_origin',
         | 
| @@ -120,6 +128,7 @@ by the #{arg.upcase.gsub('-','_')} environment variable" if | |
| 120 128 | 
             
                options[arg].nil? or ! options[arg]
         | 
| 121 129 | 
             
            end
         | 
| 122 130 |  | 
| 131 | 
            +
             | 
| 123 132 | 
             
            # convert tags from string to ruby hash
         | 
| 124 133 | 
             
            tags = {}
         | 
| 125 134 | 
             
            if options['tags']
         | 
| @@ -130,6 +139,30 @@ if options['tags'] | |
| 130 139 | 
             
            end
         | 
| 131 140 | 
             
            options['tags'] = tags
         | 
| 132 141 |  | 
| 133 | 
            -
            stemcell = Stemcell::Stemcell.new(options)
         | 
| 134 | 
            -
            stemcell.launch({'count' => options['count']})
         | 
| 135 142 |  | 
| 143 | 
            +
            # convert security_groups from comma seperated string to ruby array
         | 
| 144 | 
            +
            options['security_groups'] = options['security_groups'].split(',')
         | 
| 145 | 
            +
             | 
| 146 | 
            +
             | 
| 147 | 
            +
            # create stemcell object
         | 
| 148 | 
            +
            stemcell = Stemcell::Stemcell.new({
         | 
| 149 | 
            +
              'aws_access_key' => options['aws_access_key'],
         | 
| 150 | 
            +
              'aws_secret_key' => options['aws_secret_key'],
         | 
| 151 | 
            +
              'region'         => options['region'],
         | 
| 152 | 
            +
            })
         | 
| 153 | 
            +
             | 
| 154 | 
            +
             | 
| 155 | 
            +
            # launch instance(s)
         | 
| 156 | 
            +
            stemcell.launch({
         | 
| 157 | 
            +
              'instance_type'         => options['instance_type'],
         | 
| 158 | 
            +
              'image_id'             => options['image_id'],
         | 
| 159 | 
            +
              'security_groups'      => options['security_groups'],
         | 
| 160 | 
            +
              'chef_role'            => options['chef_role'],
         | 
| 161 | 
            +
              'chef_environment'     => options['chef_environment'],
         | 
| 162 | 
            +
              'chef_data_bag_secret' => options['chef_data_bag_secret'],
         | 
| 163 | 
            +
              'git_branch'           => options['git_branch'],
         | 
| 164 | 
            +
              'git_key'              => options['git_key'],
         | 
| 165 | 
            +
              'git_origin'           => options['git_origin'],
         | 
| 166 | 
            +
              'key_name'             => options['key_name'],
         | 
| 167 | 
            +
              'count'                => options['count'],
         | 
| 168 | 
            +
            })
         | 
    
        data/examples/stemcellrc
    ADDED
    
    | @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            # these options need to be set
         | 
| 2 | 
            +
            export AWS_ACCESS_KEY=your_aws_access_key
         | 
| 3 | 
            +
            export AWS_SECRET_KEY=your_aws_secret_key
         | 
| 4 | 
            +
            export KEY_NAME=your_aws_ssh_key_name
         | 
| 5 | 
            +
            export CHEF_DATA_BAG_SECRET=/path/to/encrypted/data/bag/secret
         | 
| 6 | 
            +
            export GIT_KEY=/path/to/chef/deploy/key
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # these options should not need to be set
         | 
| 9 | 
            +
            export SECURITY_GROUP=default
         | 
| 10 | 
            +
            export GIT_ORIGIN=git@github.com:airbnb/chef.git
         | 
| @@ -31,12 +31,14 @@ exec 2> /var/log/init.err | |
| 31 31 |  | 
| 32 32 |  | 
| 33 33 | 
             
            chef_version=11.4.0
         | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 34 | 
            +
            chef_dir=/etc/chef
         | 
| 35 | 
            +
            converge=/usr/local/bin/converge
         | 
| 36 | 
            +
            role=<%= opts['chef_role'] %>
         | 
| 37 | 
            +
            environment=<%= opts['chef_environment'] %>
         | 
| 38 | 
            +
            origin=<%= opts['git_origin'] %>
         | 
| 39 | 
            +
            branch=<%= opts['git_branch'] %>
         | 
| 40 | 
            +
            git_key='<%= opts["git_key"] %>'
         | 
| 41 | 
            +
            data_bag_secret='<%= opts["chef_data_bag_secret"] %>'
         | 
| 40 42 |  | 
| 41 43 |  | 
| 42 44 | 
             
            ##
         | 
| @@ -69,8 +71,6 @@ install_chef() { | |
| 69 71 | 
             
              if ! which chef-solo > /dev/null ; then
         | 
| 70 72 | 
             
                echo installing chef via omnibus...
         | 
| 71 73 | 
             
                curl -L --silent https://www.opscode.com/chef/install.sh | sudo bash -s -- -v $chef_version  1>&2
         | 
| 72 | 
            -
                # TODO(mkr): this is a hack to get chef-solo-search working
         | 
| 73 | 
            -
                /opt/chef/embedded/bin/gem install --no-ri --no-rdoc treetop
         | 
| 74 74 | 
             
              else
         | 
| 75 75 | 
             
                echo chef is already installed
         | 
| 76 76 | 
             
              fi
         | 
| @@ -78,80 +78,86 @@ install_chef() { | |
| 78 78 |  | 
| 79 79 |  | 
| 80 80 | 
             
            update_repo() {
         | 
| 81 | 
            -
               | 
| 82 | 
            -
               | 
| 81 | 
            +
              echo -e "$git_key" > $keyfile
         | 
| 82 | 
            +
              chmod 0400 $keyfile
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              echo "ssh -i $keyfile -o StrictHostKeyChecking=no \$1 \$2" > $git_wrapper
         | 
| 85 | 
            +
              chmod 0500 $git_wrapper
         | 
| 86 | 
            +
             | 
| 83 87 | 
             
              mkdir -p $(dirname $repo_dir)
         | 
| 84 | 
            -
              if [ -d $repo_dir ]; then
         | 
| 85 | 
            -
                echo  | 
| 86 | 
            -
                (cd $repo_dir && GIT_SSH=$git_wrapper git fetch && git reset --hard origin/$branch && git clean -fdx)
         | 
| 87 | 
            -
              else
         | 
| 88 | 
            -
                echo -e "$git_key" > $keyfile
         | 
| 89 | 
            -
                chmod 0400 $keyfile
         | 
| 90 | 
            -
                echo "ssh -i $keyfile  -o StrictHostKeyChecking=no \$1 \$2" > $git_wrapper
         | 
| 91 | 
            -
                chmod 0500 $git_wrapper
         | 
| 92 | 
            -
                echo downloading cookbook repo...
         | 
| 88 | 
            +
              if [ ! -d $repo_dir ]; then
         | 
| 89 | 
            +
                echo "downloading cookbook repo..."
         | 
| 93 90 | 
             
                GIT_SSH=$git_wrapper git clone --branch $branch --depth 1 $origin $repo_dir
         | 
| 91 | 
            +
              else
         | 
| 92 | 
            +
                echo "cookbook repo already exists"
         | 
| 94 93 | 
             
              fi
         | 
| 95 | 
            -
             | 
| 94 | 
            +
             | 
| 95 | 
            +
              echo "done updating code"
         | 
| 96 96 | 
             
            }
         | 
| 97 97 |  | 
| 98 98 |  | 
| 99 99 | 
             
            configure_chef() {
         | 
| 100 | 
            -
              echo  | 
| 101 | 
            -
               | 
| 102 | 
            -
               | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
              " | 
| 111 | 
            -
               | 
| 112 | 
            -
            }
         | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
                cat<<EOF>/etc/chef/solo.json
         | 
| 117 | 
            -
            {
         | 
| 118 | 
            -
              "run_list": "role[$role]",
         | 
| 119 | 
            -
              "branch": "$branch",
         | 
| 120 | 
            -
              "role": "$role"
         | 
| 121 | 
            -
            }
         | 
| 122 | 
            -
            EOF
         | 
| 123 | 
            -
              fi
         | 
| 100 | 
            +
              echo "saving current branch ($branch), role ($role), and environment ($environment)"
         | 
| 101 | 
            +
              echo "$branch" > $branchfile
         | 
| 102 | 
            +
              chmod 644 $branchfile
         | 
| 103 | 
            +
             | 
| 104 | 
            +
              echo "$role" > $rolefile
         | 
| 105 | 
            +
              chmod 644 $rolefile
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              echo "$environment" > $envfile
         | 
| 108 | 
            +
              chmod 644 $envfile
         | 
| 109 | 
            +
             | 
| 110 | 
            +
              echo "configuring chef solo..."
         | 
| 111 | 
            +
              cat<<EOF > ${chef_dir}/solo.rb
         | 
| 112 | 
            +
            repo_dir =    "${repo_dir}"
         | 
| 113 | 
            +
            cookbook_path "#{repo_dir}/cookbooks"
         | 
| 114 | 
            +
            role_path     "#{repo_dir}/roles"
         | 
| 115 | 
            +
            data_bag_path "#{repo_dir}/data_bags"
         | 
| 124 116 |  | 
| 125 | 
            -
              cat<<EOF>/etc/chef/solo.rb
         | 
| 126 117 | 
             
            log_level        :info
         | 
| 127 118 | 
             
            log_location     STDOUT
         | 
| 128 | 
            -
            cookbook_path "$repo_dir/cookbooks"
         | 
| 129 | 
            -
            role_path "$repo_dir/roles"
         | 
| 130 | 
            -
            data_bag_path "$repo_dir/data_bags"
         | 
| 131 | 
            -
            json_attribs '/etc/chef/solo.json'
         | 
| 132 119 | 
             
            EOF
         | 
| 133 | 
            -
             | 
| 134 | 
            -
              echo  | 
| 120 | 
            +
             | 
| 121 | 
            +
              echo -e "$data_bag_secret" > ${chef_dir}/encrypted_data_bag_secret
         | 
| 122 | 
            +
              echo "chef configured"
         | 
| 135 123 | 
             
            }
         | 
| 136 124 |  | 
| 137 125 |  | 
| 138 126 | 
             
            configure_converger() {
         | 
| 139 | 
            -
            cat<<EOF | 
| 140 | 
            -
            #!/bin/bash - | 
| 127 | 
            +
              cat<<EOF > $converge
         | 
| 128 | 
            +
            #!/bin/bash -eu
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            repo="${repo_dir}"
         | 
| 131 | 
            +
            branch=\`cat ${branchfile}\`
         | 
| 132 | 
            +
            role=\`cat ${rolefile}\`
         | 
| 133 | 
            +
            env=\`cat ${envfile}\`
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            cd \${repo}
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            runlist="role[\${role}]"
         | 
| 138 | 
            +
            if [ -f "roles/\${env}.rb" ]
         | 
| 139 | 
            +
            then
         | 
| 140 | 
            +
              runlist+=",role[\${env}]"
         | 
| 141 | 
            +
            fi
         | 
| 141 142 |  | 
| 142 | 
            -
            echo doing a hard reset to origin | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
            git reset --hard origin/$branch
         | 
| 143 | 
            +
            echo doing a hard reset to origin/\${branch}
         | 
| 144 | 
            +
            GIT_SSH=${git_wrapper} git fetch
         | 
| 145 | 
            +
            git reset --hard origin/\${branch}
         | 
| 146 146 | 
             
            git clean -fdx
         | 
| 147 | 
            -
             | 
| 147 | 
            +
             | 
| 148 | 
            +
            json="{\"role\": \"\${role}\", \"env\": \"\${env}\", \"branch\": \"\${branch}\"}"
         | 
| 149 | 
            +
            json_file=\`tempfile\`
         | 
| 150 | 
            +
            echo \$json > \$json_file
         | 
| 151 | 
            +
            trap "{ rm -f '\$json_file' ; }" EXIT
         | 
| 152 | 
            +
             | 
| 153 | 
            +
            echo "running chef-solo with runlist \$runlist and json \$json (in file \$json_file)"
         | 
| 154 | 
            +
            chef-solo -o "\${runlist}" -j \${json_file} "\$@"
         | 
| 148 155 | 
             
            EOF
         | 
| 149 | 
            -
              chmod  | 
| 156 | 
            +
              chmod 544 $converge
         | 
| 150 157 | 
             
            }
         | 
| 151 158 |  | 
| 152 | 
            -
             | 
| 153 159 | 
             
            configure_chef_daemon() {
         | 
| 154 | 
            -
              cat<<EOF | 
| 160 | 
            +
              cat<<EOF > /etc/init/chef-solo.conf
         | 
| 155 161 | 
             
            description     "chef-solo"
         | 
| 156 162 | 
             
            author          "Martin Rhoads"
         | 
| 157 163 | 
             
            start on networking
         | 
| @@ -164,12 +170,11 @@ EOF | |
| 164 170 |  | 
| 165 171 |  | 
| 166 172 | 
             
            run_chef() {
         | 
| 167 | 
            -
              echo  | 
| 168 | 
            -
               | 
| 169 | 
            -
              echo  | 
| 173 | 
            +
              echo "doing initial chef run..."
         | 
| 174 | 
            +
              $converge 1>&2
         | 
| 175 | 
            +
              echo "initial chef run complete"
         | 
| 170 176 | 
             
            }
         | 
| 171 177 |  | 
| 172 | 
            -
             | 
| 173 178 | 
             
            start_chef_daemon() {
         | 
| 174 179 | 
             
              start chef-solo
         | 
| 175 180 | 
             
            }
         | 
| @@ -179,8 +184,16 @@ start_chef_daemon() { | |
| 179 184 | 
             
            ## main
         | 
| 180 185 | 
             
            ##
         | 
| 181 186 |  | 
| 187 | 
            +
            #some derived vars
         | 
| 188 | 
            +
            repo_dir=${chef_dir}/src
         | 
| 189 | 
            +
            keyfile=${chef_dir}/git_key
         | 
| 190 | 
            +
            envfile=${chef_dir}/environment
         | 
| 191 | 
            +
            rolefile=${chef_dir}/role
         | 
| 192 | 
            +
            branchfile=${chef_dir}/branch
         | 
| 193 | 
            +
            git_wrapper=${chef_dir}/git_wrapper
         | 
| 182 194 |  | 
| 183 | 
            -
            echo starting chef bootstrapping...
         | 
| 195 | 
            +
            echo "starting chef bootstrapping..."
         | 
| 196 | 
            +
            mkdir -p ${chef_dir}
         | 
| 184 197 | 
             
            update
         | 
| 185 198 | 
             
            install curl
         | 
| 186 199 | 
             
            install git
         | 
| @@ -190,6 +203,7 @@ update_repo | |
| 190 203 | 
             
            configure_chef
         | 
| 191 204 | 
             
            configure_converger
         | 
| 192 205 | 
             
            run_chef
         | 
| 206 | 
            +
             | 
| 193 207 | 
             
            configure_chef_daemon
         | 
| 194 208 | 
             
            # start_chef_daemon
         | 
| 195 209 |  | 
    
        data/lib/stemcell/version.rb
    CHANGED
    
    
    
        data/lib/stemcell.rb
    CHANGED
    
    | @@ -8,65 +8,81 @@ module Stemcell | |
| 8 8 | 
             
              class Stemcell
         | 
| 9 9 | 
             
                def initialize(opts={})
         | 
| 10 10 | 
             
                  @log = Logger.new(STDOUT)
         | 
| 11 | 
            +
                  @log.level = Logger::INFO unless ENV['DEBUG']
         | 
| 12 | 
            +
                  @log.debug "creating new stemcell object"
         | 
| 11 13 | 
             
                  @log.debug "opts are #{opts.inspect}"
         | 
| 12 14 | 
             
                  ['aws_access_key',
         | 
| 13 15 | 
             
                   'aws_secret_key',
         | 
| 14 16 | 
             
                   'region',
         | 
| 15 | 
            -
                   'machine_type',
         | 
| 16 | 
            -
                   'image',
         | 
| 17 | 
            -
                   'security_group',
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                   'chef_role',
         | 
| 20 | 
            -
                   'git_branch',
         | 
| 21 | 
            -
                   'git_key',
         | 
| 22 | 
            -
                   'git_origin',
         | 
| 23 | 
            -
                   'key_name',
         | 
| 24 17 | 
             
                  ].each do |req|
         | 
| 25 18 | 
             
                    raise ArgumentError, "missing required param #{req}" unless opts[req]
         | 
| 26 19 | 
             
                    instance_variable_set("@#{req}",opts[req])
         | 
| 27 20 | 
             
                  end
         | 
| 28 21 |  | 
| 29 | 
            -
                  @zone = opts.include?('availability_zone') ? opts['availability_zone'] : nil
         | 
| 30 22 | 
             
                  @ec2_url = "ec2.#{@region}.amazonaws.com"
         | 
| 31 23 | 
             
                  @timeout = 120
         | 
| 32 24 | 
             
                  @start_time = Time.new
         | 
| 33 25 |  | 
| 34 | 
            -
                  @tags = {
         | 
| 35 | 
            -
                    'Name' => "#{@chef_role}-#{@git_branch}",
         | 
| 36 | 
            -
                    'Group' => "#{@chef_role}-#{@git_branch}",
         | 
| 37 | 
            -
                    'created_by' => ENV['USER'],
         | 
| 38 | 
            -
                    'stemcell' => VERSION,
         | 
| 39 | 
            -
                  }
         | 
| 40 | 
            -
                  @tags.merge!(opts['tags']) if opts['tags']
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  begin
         | 
| 43 | 
            -
                    @git_key_contents = File.read(@git_key)
         | 
| 44 | 
            -
                  rescue Object => e
         | 
| 45 | 
            -
                    @git_key_contents = @git_key  # assume content is passed in
         | 
| 46 | 
            -
                  end
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                  if opts.include?('chef_data_bag_secret')
         | 
| 49 | 
            -
                    begin
         | 
| 50 | 
            -
                      @chef_data_bag_secret = File.read(opts['chef_data_bag_secret'])
         | 
| 51 | 
            -
                    rescue Object => e
         | 
| 52 | 
            -
                      @chef_data_bag_secret = opts['chef_data_bag_secret'] # assume secret is passed in
         | 
| 53 | 
            -
                    end
         | 
| 54 | 
            -
                  else
         | 
| 55 | 
            -
                    @chef_data_bag_secret = ''
         | 
| 56 | 
            -
                  end
         | 
| 57 | 
            -
             | 
| 58 26 | 
             
                  AWS.config({:access_key_id => @aws_access_key, :secret_access_key => @aws_secret_key})
         | 
| 59 27 | 
             
                  @ec2 = AWS::EC2.new(:ec2_endpoint => @ec2_url)
         | 
| 60 28 | 
             
                  @ec2_region = @ec2.regions[@region]
         | 
| 61 | 
            -
                  @user_data = render_template
         | 
| 62 29 | 
             
                end
         | 
| 63 30 |  | 
| 31 | 
            +
             | 
| 64 32 | 
             
                def launch(opts={})
         | 
| 65 | 
            -
                   | 
| 66 | 
            -
             | 
| 33 | 
            +
                  verify_required_options(opts,[
         | 
| 34 | 
            +
                    'image_id',
         | 
| 35 | 
            +
                    'security_groups',
         | 
| 36 | 
            +
                    'key_name',
         | 
| 37 | 
            +
                    'count',
         | 
| 38 | 
            +
                    'chef_role',
         | 
| 39 | 
            +
                    'chef_environment',
         | 
| 40 | 
            +
                    'chef_data_bag_secret',
         | 
| 41 | 
            +
                    'git_branch',
         | 
| 42 | 
            +
                    'git_key',
         | 
| 43 | 
            +
                    'git_origin',
         | 
| 44 | 
            +
                    'instance_type',
         | 
| 45 | 
            +
                  ])
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # attempt to accept keys as file paths
         | 
| 48 | 
            +
                  opts['git_key'] = try_file(opts['git_key'])
         | 
| 49 | 
            +
                  opts['chef_data_bag_secret'] = try_file(opts['chef_data_bag_secret'])
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  # generate tags and merge in any that were specefied as in inputs
         | 
| 52 | 
            +
                  tags = {
         | 
| 53 | 
            +
                    'Name' => "#{opts['chef_role']}-#{opts['chef_environment']}",
         | 
| 54 | 
            +
                    'Group' => "#{opts['chef_role']}-#{opts['chef_environment']}",
         | 
| 55 | 
            +
                    'created_by' => ENV['USER'],
         | 
| 56 | 
            +
                    'stemcell' => VERSION,
         | 
| 57 | 
            +
                  }
         | 
| 58 | 
            +
                  tags.merge!(opts['tags']) if opts['tags']
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # generate user data script to boot strap instance based on the
         | 
| 61 | 
            +
                  # opts that we were passed.
         | 
| 62 | 
            +
                  user_data = render_template(opts)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  launch_options = {
         | 
| 65 | 
            +
                    :image_id => opts['image_id'],
         | 
| 66 | 
            +
                    :security_groups => opts['security_groups'],
         | 
| 67 | 
            +
                    :user_data => opts['user_data'],
         | 
| 68 | 
            +
                    :instance_type => opts['instance_type'],
         | 
| 69 | 
            +
                    :key_name => opts['key_name'],
         | 
| 70 | 
            +
                    :count => opts['count'],
         | 
| 71 | 
            +
                    :user_data => user_data,
         | 
| 72 | 
            +
                  }
         | 
| 73 | 
            +
                  launch_options.merge({:availability_zone => opts['availability_zone']}) if opts['availability_zone']
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  # launch instances
         | 
| 76 | 
            +
                  instances = do_launch(launch_options)
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  # wait for aws to report instance stats
         | 
| 67 79 | 
             
                  wait(instances)
         | 
| 68 | 
            -
             | 
| 80 | 
            +
             | 
| 81 | 
            +
                  # set tags on all instances launched
         | 
| 82 | 
            +
                  set_tags(instances, tags)
         | 
| 83 | 
            +
             | 
| 69 84 | 
             
                  print_run_info(instances)
         | 
| 85 | 
            +
                  @log.info "launched instances successfully"
         | 
| 70 86 | 
             
                  return instances
         | 
| 71 87 | 
             
                end
         | 
| 72 88 |  | 
| @@ -83,7 +99,7 @@ module Stemcell | |
| 83 99 | 
             
                end
         | 
| 84 100 |  | 
| 85 101 | 
             
                def wait(instances)
         | 
| 86 | 
            -
                  @log.info "Waiting for #{instances.count} instances (#{instances.inspect}):"
         | 
| 102 | 
            +
                  @log.info "Waiting up to #{@timeout} seconds for #{instances.count} instances (#{instances.inspect}):"
         | 
| 87 103 |  | 
| 88 104 | 
             
                  while true
         | 
| 89 105 | 
             
                    sleep 5
         | 
| @@ -100,18 +116,18 @@ module Stemcell | |
| 100 116 | 
             
                  @log.info "all instances in running state"
         | 
| 101 117 | 
             
                end
         | 
| 102 118 |  | 
| 103 | 
            -
                def  | 
| 104 | 
            -
                   | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                     | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
                  }
         | 
| 111 | 
            -
                  options[:availability_zone] = @zone if @zone
         | 
| 112 | 
            -
                  options[:count] = opts['count'] if opts.include?('count')
         | 
| 119 | 
            +
                def verify_required_options(params,required_options)
         | 
| 120 | 
            +
                  @log.debug "params is #{params}"
         | 
| 121 | 
            +
                  @log.debug "required_options are #{required_options}"
         | 
| 122 | 
            +
                  required_options.each do |required|
         | 
| 123 | 
            +
                    raise ArgumentError, "you need to provide option #{required}" unless params[required]
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
                end
         | 
| 113 126 |  | 
| 114 | 
            -
             | 
| 127 | 
            +
                def do_launch(opts={})
         | 
| 128 | 
            +
                  @log.debug "about to launch instance(s) with options #{opts}"
         | 
| 129 | 
            +
                  @log.info "launching instances"
         | 
| 130 | 
            +
                  instances = @ec2_region.instances.create(opts)
         | 
| 115 131 | 
             
                  instances = [instances] unless instances.class == Array
         | 
| 116 132 | 
             
                  instances.each do |instance|
         | 
| 117 133 | 
             
                    @log.info "launched instance #{instance.instance_id}"
         | 
| @@ -119,19 +135,21 @@ module Stemcell | |
| 119 135 | 
             
                  return instances
         | 
| 120 136 | 
             
                end
         | 
| 121 137 |  | 
| 122 | 
            -
                def set_tags(instances=[])
         | 
| 138 | 
            +
                def set_tags(instances=[],tags)
         | 
| 139 | 
            +
                  @log.info "setting tags on instance(s)"
         | 
| 123 140 | 
             
                  instances.each do |instance|
         | 
| 124 | 
            -
                    instance.tags.set( | 
| 141 | 
            +
                    instance.tags.set(tags)
         | 
| 125 142 | 
             
                  end
         | 
| 126 143 | 
             
                end
         | 
| 127 144 |  | 
| 128 | 
            -
                def render_template
         | 
| 145 | 
            +
                def render_template(opts={})
         | 
| 129 146 | 
             
                  this_file = File.expand_path __FILE__
         | 
| 130 147 | 
             
                  base_dir = File.dirname this_file
         | 
| 131 148 | 
             
                  template_file_path = File.join(base_dir,'stemcell','templates','bootstrap.sh.erb')
         | 
| 132 149 | 
             
                  template_file = File.read(template_file_path)
         | 
| 133 150 | 
             
                  erb_template = ERB.new(template_file)
         | 
| 134 151 | 
             
                  generated_template = erb_template.result(binding)
         | 
| 152 | 
            +
                  @log.debug "genereated template is #{generated_template}"
         | 
| 135 153 | 
             
                  return generated_template
         | 
| 136 154 | 
             
                end
         | 
| 137 155 |  | 
| @@ -142,5 +160,15 @@ module Stemcell | |
| 142 160 | 
             
                    instance.delete
         | 
| 143 161 | 
             
                  end
         | 
| 144 162 | 
             
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                # attempt to accept keys as file paths
         | 
| 165 | 
            +
                def try_file(opt="")
         | 
| 166 | 
            +
                  begin
         | 
| 167 | 
            +
                    return File.read(opt)
         | 
| 168 | 
            +
                  rescue Object => e
         | 
| 169 | 
            +
                    return opt
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
             | 
| 145 173 | 
             
              end
         | 
| 146 174 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: stemcell
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors:
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2013-02- | 
| 12 | 
            +
            date: 2013-02-28 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: trollop
         | 
| @@ -57,6 +57,7 @@ files: | |
| 57 57 | 
             
            - README.md
         | 
| 58 58 | 
             
            - Rakefile
         | 
| 59 59 | 
             
            - bin/stemcell
         | 
| 60 | 
            +
            - examples/stemcellrc
         | 
| 60 61 | 
             
            - lib/stemcell.rb
         | 
| 61 62 | 
             
            - lib/stemcell/templates/bootstrap.sh.erb
         | 
| 62 63 | 
             
            - lib/stemcell/version.rb
         |