Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
*.pyc
.cache/
.coverage
.tox/
dist/
*.egg-info
*.swp
5 changes: 5 additions & 0 deletions examples/Vagrant_ansible/deploy/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- hosts: "{{target|default('localhost')}}"
become: yes
tasks:
- apt: name=tree state=installed
71 changes: 71 additions & 0 deletions examples/Vagrant_ansible/striderfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python
# example strider file

from strider import Strider
from strider.virt.vagrantbox import Vagrantbox
from strider.provisioners.shell import Shell
import time
import os

# ==============================================================================
# vagrant instance and builder configuration

instance = Vagrantbox(
name = "%s-strider-test" % os.environ["USER"], # currently not used
basebox = "wheezy", # the basebox to use
ssh = dict(
username = "vagrant",# default username to login with
private_key_path = "/Users/jtang/.vagrant.d/insecure_private_key", # key to use
),
bake_name = "strider-produced-basebox-%d" % int(time.time()), # output filename
bake_description = "Vagrant basebox description goes here version 1.00",

# other optional parameters:
user_data = open("userdata.sh").read(),
)

# ==============================================================================
# The provisioner decides how the instance will be configured

provisioner = Shell(
commands = [

# you can deselect 'rsync' by changing to 'copy' below for the type parameter.
# rsync on AWS free tier has been observed to be unreliable - protocol errors
# so if you see this, know why

"sudo DEBIAN_FRONTEND=noninteractive apt-get update -y",
"sudo DEBIAN_FRONTEND=noninteractive apt-get -y install rsync",
dict(type='rsync', copy_from="./deploy", copy_to="/home/vagrant/deploy_root"),
]
)

# Alternative: run ansible remotely from the build machine / workstation
# (more SSH activity, but shares less information with the guest)

# provisioner = Shell(
# commands = [
# dict(type='command',
# command='PYTHONUNBUFFERED=1 ansible-playbook -v -i {{ssh_host}}, -u {{ssh_user}} -e "target={{ssh_host}}" deploy/test.yml'),

# ]
# )

# optional steps to run prior to --bake commands that will only run with --bake
pre_bake = Shell(
commands = [
"sync"
]
)

# optional commands to run after successful bake jobs to use remaining compute time
post_bake = Shell(
commands = [
"echo 'post bake steps!'"
]
)

# =============================================================================
# go!

strider = Strider(provisioner=provisioner, pre_bake=pre_bake, post_bake=post_bake).cli(instance)
6 changes: 6 additions & 0 deletions examples/Vagrant_ansible/userdata.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
SUDOERS_FILE=/etc/sudoers.d/strider-init-requiretty
echo "Defaults:ec2-user !requiretty" > $SUDOERS_FILE
echo "Defaults:root !requiretty" >> $SUDOERS_FILE
chmod 440 $SUDOERS_FILE

85 changes: 85 additions & 0 deletions lib/strider/virt/vagrantbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import vagrant
import os
from subprocess import CalledProcessError
from strider.common.instance_data import InstanceData, SshData
import strider.common.logger


class Vagrantbox(object):
def __init__(self,
name=None,
ssh=None,
basebox=None,
bake_name=None,
bake_description=None,
user_data=None):
self.name = name
self.bake_name = bake_name
self.basebox = basebox
self.ssh = ssh
self.log = strider.utils.logger.get_logger('Vagrant')
if type(self.ssh) != dict:
raise Exception("expecting 'ssh' to be a dictionary")

self.vagrant_instance = vagrant.Vagrant()

def describe(self):
details = self._details()
if details is None:
return InstanceData(present=False)
else:
if self.ssh['username'] is not None:
username = self.ssh['username']
else:
username = "vagrant"

if self.ssh['private_key_path'] is not None:
private_key_path = self.ssh['private_key_path']
else:
private_key_path = details['IdentityFile']

port = details['Port']
host = details['HostName']
ssh_data = SshData(keyfile=private_key_path,
user=username,
host=host,
port=port)
return InstanceData(present=True,
provider_specific=details,
ssh=ssh_data)

def destroy(self):
self.log("destroying instance")
try:
self.vagrant_instance.destroy()
except CalledProcessError:
self.log("already destroyed instance")
try:
os.remove("./Vagrantfile")
except OSError:
self.log("already removed Vagrantfile")

def up(self):
self.log("determining if we need to create an instance")
try:
self.vagrant_instance.init(box_name=self.basebox)
except CalledProcessError:
self.log("already initialised instance")
try:
self.log("bring up instance")
self.vagrant_instance.up()
except CalledProcessError:
self.log("already up")

def _details(self):
try:
conf = self.vagrant_instance.conf()
return conf
except CalledProcessError:
self.log("No instance running")
return None

def bake(self):
self.log("baking vagrant box")
os.system("vagrant package --output {}.box".format(self.bake_name))
self.up()
118 changes: 118 additions & 0 deletions lib/strider/virt/virtualbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright 2015 Gerard Cristofol <gcristofol/gmail>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Import the vboxapi package
from vboxapi import VirtualBoxManager

import socket
import time
from strider.common.instance_data import InstanceData, SshData
import strider.common.logger

class VBOX(object):

def __init__(self, name=None, instance_type=None, virtualbox_manager=None, tags=None):

self.name = name
self.instance_type = instance_type
self.tags = {}
self.virtualbox_manager = virtualbox_manager

# utility instances
self.log = strider.utils.logger.get_logger('VBOX')

# check for required args
if not self.name:
raise Exception("'name' is required")
if not self.instance_type:
raise Exception("'instance_type' is required")

# coerce inputs
self.tags['Name'] = self.name


# --------------------------------------------------------------------------
# PUBLIC VIRT API INTERFACE
# --------------------------------------------------------------------------

def up(self):
""" Instantiate instances if needed, otherwise just start them """

self.log("determining if we need to create an instance")
me = self.describe().provider_specific
if me is None:
self.log("creating an instance")

#TODO Create the virtualbox instance
# 2. Get the IVirtualBox object
virtualbox = self.virtualbox_manager.getVirtualBox()
virtualbox.createInstance() #parameter home???

self.log("instance created")
else:
self.log("instance already exists, starting if needed")
self.connection.start_instances([me.id])

me = self.describe()
if not me.present:
raise Exception("unexpectedly can't find the instance.")

# --------------------------------------------------------------------------

def destroy(self):
""" Destroy the described instance """

self.log("looking for instances to destroy")
me = self.describe()
if me.present:
self.log("destroying instance")
self.connection.terminate_instances(instance_ids=[me.provider_specific.id])
self.log("instance destroyed")
else:
self.log("no instance found to destroy")

# --------------------------------------------------------------------------

def describe(self):
""" Return details about the instance. Standardized between cloud providers """

details = self._details()
if details is None:
return InstanceData(present=False)

# --------------------------------------------------------------------------
# PRIVATE FUNCTIONS
# --------------------------------------------------------------------------


def _details(self):
""" Return the cloud provider's info about the described instance"""

# 2. Get the IVirtualBox object
virtualbox = self.virtualbox_manager.getVirtualBox()

# For IVirtualBox array attributes you need to use
# the VirtualBoxManager.getArray() method:

# Iterate the IVirtualBox::machines array.
# Each element is an IMachine instance.
machines = self.virtualbox_manager.getArray(virtualbox, 'machines')
for machine in machines:
print 'Virtual machine, name: ', machine.name
if machine.name is self.name:
return machine

return None


2 changes: 2 additions & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest
coverage
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
from setuptools import setup, find_packages

setup(name='strider',
version="0.0.11",
version="0.0.12",
description='Strider builds dev VMs and bakes cloud images.',
author='Michael DeHaan',
author_email='michael.dehaan@gmail.com',
url='http://github.com/mpdehaan/strider/',
license='Apache2',
install_requires=['boto'],
install_requires=['boto', 'python-vagrant'],
package_dir={ '': 'lib' },
packages=find_packages('lib'),
classifiers=[
Expand Down
4 changes: 4 additions & 0 deletions tests/strider_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import pytest

def test_strider():
pass
13 changes: 13 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[tox]
envlist = py27

[testenv]
setenv =
PYTHONDONTWRITEBYTECODE = 1
commands = coverage run --source=lib -m py.test
coverage report
deps=-rrequirements_test.txt

[pytest]
addopts = tests
pep8maxlinelength = 120