vagrant 0.1.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/.gitignore +11 -0
- data/Gemfile +17 -0
- data/README.md +45 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/bin/.gitignore +0 -0
- data/bin/vagrant +15 -0
- data/bin/vagrant-box +35 -0
- data/bin/vagrant-down +28 -0
- data/bin/vagrant-halt +29 -0
- data/bin/vagrant-init +28 -0
- data/bin/vagrant-package +30 -0
- data/bin/vagrant-reload +30 -0
- data/bin/vagrant-resume +28 -0
- data/bin/vagrant-ssh +28 -0
- data/bin/vagrant-suspend +28 -0
- data/bin/vagrant-up +30 -0
- data/config/default.rb +29 -0
- data/lib/vagrant.rb +14 -0
- data/lib/vagrant/actions/base.rb +93 -0
- data/lib/vagrant/actions/box/add.rb +22 -0
- data/lib/vagrant/actions/box/destroy.rb +14 -0
- data/lib/vagrant/actions/box/download.rb +63 -0
- data/lib/vagrant/actions/box/unpackage.rb +49 -0
- data/lib/vagrant/actions/runner.rb +128 -0
- data/lib/vagrant/actions/vm/destroy.rb +14 -0
- data/lib/vagrant/actions/vm/down.rb +12 -0
- data/lib/vagrant/actions/vm/export.rb +41 -0
- data/lib/vagrant/actions/vm/forward_ports.rb +32 -0
- data/lib/vagrant/actions/vm/halt.rb +14 -0
- data/lib/vagrant/actions/vm/import.rb +17 -0
- data/lib/vagrant/actions/vm/move_hard_drive.rb +53 -0
- data/lib/vagrant/actions/vm/package.rb +61 -0
- data/lib/vagrant/actions/vm/provision.rb +71 -0
- data/lib/vagrant/actions/vm/reload.rb +17 -0
- data/lib/vagrant/actions/vm/resume.rb +16 -0
- data/lib/vagrant/actions/vm/shared_folders.rb +69 -0
- data/lib/vagrant/actions/vm/start.rb +50 -0
- data/lib/vagrant/actions/vm/suspend.rb +16 -0
- data/lib/vagrant/actions/vm/up.rb +35 -0
- data/lib/vagrant/box.rb +129 -0
- data/lib/vagrant/busy.rb +73 -0
- data/lib/vagrant/commands.rb +174 -0
- data/lib/vagrant/config.rb +156 -0
- data/lib/vagrant/downloaders/base.rb +13 -0
- data/lib/vagrant/downloaders/file.rb +21 -0
- data/lib/vagrant/downloaders/http.rb +47 -0
- data/lib/vagrant/env.rb +140 -0
- data/lib/vagrant/ssh.rb +43 -0
- data/lib/vagrant/util.rb +45 -0
- data/lib/vagrant/vm.rb +57 -0
- data/script/vagrant-ssh-expect.sh +22 -0
- data/templates/Vagrantfile +8 -0
- data/test/test_helper.rb +91 -0
- data/test/vagrant/actions/base_test.rb +32 -0
- data/test/vagrant/actions/box/add_test.rb +37 -0
- data/test/vagrant/actions/box/destroy_test.rb +18 -0
- data/test/vagrant/actions/box/download_test.rb +118 -0
- data/test/vagrant/actions/box/unpackage_test.rb +101 -0
- data/test/vagrant/actions/runner_test.rb +236 -0
- data/test/vagrant/actions/vm/destroy_test.rb +24 -0
- data/test/vagrant/actions/vm/down_test.rb +32 -0
- data/test/vagrant/actions/vm/export_test.rb +88 -0
- data/test/vagrant/actions/vm/forward_ports_test.rb +50 -0
- data/test/vagrant/actions/vm/halt_test.rb +27 -0
- data/test/vagrant/actions/vm/import_test.rb +36 -0
- data/test/vagrant/actions/vm/move_hard_drive_test.rb +108 -0
- data/test/vagrant/actions/vm/package_test.rb +155 -0
- data/test/vagrant/actions/vm/provision_test.rb +103 -0
- data/test/vagrant/actions/vm/reload_test.rb +44 -0
- data/test/vagrant/actions/vm/resume_test.rb +27 -0
- data/test/vagrant/actions/vm/shared_folders_test.rb +117 -0
- data/test/vagrant/actions/vm/start_test.rb +55 -0
- data/test/vagrant/actions/vm/suspend_test.rb +27 -0
- data/test/vagrant/actions/vm/up_test.rb +76 -0
- data/test/vagrant/box_test.rb +92 -0
- data/test/vagrant/busy_test.rb +81 -0
- data/test/vagrant/commands_test.rb +252 -0
- data/test/vagrant/config_test.rb +123 -0
- data/test/vagrant/downloaders/base_test.rb +20 -0
- data/test/vagrant/downloaders/file_test.rb +32 -0
- data/test/vagrant/downloaders/http_test.rb +40 -0
- data/test/vagrant/env_test.rb +293 -0
- data/test/vagrant/ssh_test.rb +95 -0
- data/test/vagrant/util_test.rb +64 -0
- data/test/vagrant/vm_test.rb +96 -0
- metadata +275 -0
data/config/default.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Vagrant::Config.run do |config|
|
2
|
+
# default config goes here
|
3
|
+
config.vagrant.log_output = STDOUT
|
4
|
+
config.vagrant.dotfile_name = ".vagrant"
|
5
|
+
config.vagrant.home = "~/.vagrant"
|
6
|
+
|
7
|
+
config.ssh.username = "vagrant"
|
8
|
+
config.ssh.password = "vagrant"
|
9
|
+
config.ssh.host = "localhost"
|
10
|
+
config.ssh.forwarded_port_key = "ssh"
|
11
|
+
config.ssh.max_tries = 10
|
12
|
+
|
13
|
+
config.vm.box_ovf = "box.ovf"
|
14
|
+
config.vm.base_mac = "0800279C2E42"
|
15
|
+
config.vm.project_directory = "/vagrant"
|
16
|
+
config.vm.forward_port("ssh", 22, 2222)
|
17
|
+
config.vm.disk_image_format = 'VMDK'
|
18
|
+
|
19
|
+
config.package.name = 'vagrant'
|
20
|
+
config.package.extension = '.box'
|
21
|
+
|
22
|
+
config.chef.enabled = false
|
23
|
+
config.chef.cookbooks_path = "cookbooks"
|
24
|
+
config.chef.provisioning_path = "/tmp/vagrant-chef"
|
25
|
+
config.chef.json = {
|
26
|
+
:instance_role => "vagrant",
|
27
|
+
:recipes => ["vagrant_main"]
|
28
|
+
}
|
29
|
+
end
|
data/lib/vagrant.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
libdir = File.dirname(__FILE__)
|
2
|
+
$:.unshift(libdir)
|
3
|
+
PROJECT_ROOT = File.join(libdir, '..') unless defined?(PROJECT_ROOT)
|
4
|
+
|
5
|
+
# The libs which must be loaded prior to the rest
|
6
|
+
%w{tempfile open-uri ftools json pathname logger uri net/http virtualbox net/ssh tarruby
|
7
|
+
net/scp fileutils vagrant/util vagrant/actions/base vagrant/downloaders/base}.each do |f|
|
8
|
+
require f
|
9
|
+
end
|
10
|
+
|
11
|
+
# Glob require the rest
|
12
|
+
Dir[File.join(PROJECT_ROOT, "lib", "vagrant", "**", "*.rb")].each do |f|
|
13
|
+
require f
|
14
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
# Base class for any command actions.
|
4
|
+
#
|
5
|
+
# Actions are the smallest unit of functionality found within
|
6
|
+
# Vagrant. Vagrant composes many actions together to execute
|
7
|
+
# its complex tasks while keeping the individual pieces of a
|
8
|
+
# task as discrete reusable actions. Actions are ran exclusively
|
9
|
+
# by an {Runner action runner} which is simply a subclass of {Runner}.
|
10
|
+
#
|
11
|
+
# Actions work by implementing any or all of the following methods
|
12
|
+
# which a {Runner} executes:
|
13
|
+
#
|
14
|
+
# * `prepare` - Called once for each action before any action has `execute!`
|
15
|
+
# called. This is meant for basic setup.
|
16
|
+
# * `execute!` - This is where the meat of the action typically goes;
|
17
|
+
# the main code which executes the action.
|
18
|
+
# * `cleanup` - This is called exactly once for each action after every
|
19
|
+
# other action is completed. It is meant for cleaning up any resources.
|
20
|
+
# * `rescue` - This is called if an exception occurs in _any action_. This
|
21
|
+
# gives every other action a chance to clean itself up.
|
22
|
+
#
|
23
|
+
# For details of each step of an action, read the specific function call
|
24
|
+
# documentation below.
|
25
|
+
class Base
|
26
|
+
# The {Runner runner} which is executing the action
|
27
|
+
attr_reader :runner
|
28
|
+
|
29
|
+
# Included so subclasses don't need to include it themselves.
|
30
|
+
include Vagrant::Util
|
31
|
+
|
32
|
+
# Initialization of the action, passing any arguments which may have
|
33
|
+
# been given to the {Runner runner}. This method can be used by subclasses
|
34
|
+
# to save any of the configuration options which are passed in.
|
35
|
+
def initialize(runner, *args)
|
36
|
+
@runner = runner
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method is called once per action, allowing the action
|
40
|
+
# to setup any callbacks, add more events, etc. Prepare is
|
41
|
+
# called in the order the actions are defined, and the action
|
42
|
+
# itself has no control over this.
|
43
|
+
#
|
44
|
+
# Examples of its usage:
|
45
|
+
#
|
46
|
+
# Perhaps we need an additional action only if a configuration is set:
|
47
|
+
#
|
48
|
+
# def prepare
|
49
|
+
# @vm.actions << FooAction if Vagrant.config[:foo] == :bar
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
def prepare; end
|
53
|
+
|
54
|
+
# This method is called once, after preparing, to execute the
|
55
|
+
# actual task. This method is responsible for calling any
|
56
|
+
# callbacks. Adding new actions here will have unpredictable
|
57
|
+
# effects and should never be done.
|
58
|
+
#
|
59
|
+
# Examples of its usage:
|
60
|
+
#
|
61
|
+
# def execute!
|
62
|
+
# @vm.invoke_callback(:before_oven, "cookies")
|
63
|
+
# # Do lots of stuff here
|
64
|
+
# @vm.invoke_callback(:after_oven, "more", "than", "one", "option")
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
def execute!; end
|
68
|
+
|
69
|
+
# This method is called after all actions have finished executing.
|
70
|
+
# It is meant as a place where final cleanup code can be done, knowing
|
71
|
+
# that all other actions are finished using your data.
|
72
|
+
def cleanup; end
|
73
|
+
|
74
|
+
# This method is only called if some exception occurs in the chain
|
75
|
+
# of actions. If an exception is raised in any action in the current
|
76
|
+
# chain, then every action part of that chain has {#rescue} called
|
77
|
+
# before raising the exception further. This method should be used to
|
78
|
+
# perform any cleanup necessary in the face of errors.
|
79
|
+
#
|
80
|
+
# **Warning:** Since this method is called when an exception is already
|
81
|
+
# raised, be _extra careful_ when implementing this method to handle
|
82
|
+
# all your own exceptions, otherwise it'll mask the initially raised
|
83
|
+
# exception.
|
84
|
+
def rescue(exception); end
|
85
|
+
end
|
86
|
+
|
87
|
+
# An exception which occured within an action. This should be used instead of
|
88
|
+
# {Vagrant::Util#error_and_exit error_and_exit}, since it allows the {Runner} to call
|
89
|
+
# {Base#rescue rescue} on all the actions and properly exit. Any message
|
90
|
+
# passed into the {ActionException} is then shown and and vagrant exits.
|
91
|
+
class ActionException < Exception; end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
module Box
|
4
|
+
# A meta-action which adds a box by downloading and unpackaging it.
|
5
|
+
# This action downloads and unpackages a box with a given URI. This
|
6
|
+
# is a _meta action_, meaning it simply adds more actions to the
|
7
|
+
# action chain, and those actions do the work.
|
8
|
+
#
|
9
|
+
# This is the action called by {Box#add}.
|
10
|
+
class Add < Base
|
11
|
+
def prepare
|
12
|
+
if File.exists?(@runner.directory)
|
13
|
+
raise ActionException.new("A box with the name '#{@runner.name}' already exists, please use another name or use `vagrant box remove #{@runner.name}`")
|
14
|
+
end
|
15
|
+
|
16
|
+
@runner.add_action(Download)
|
17
|
+
@runner.add_action(Unpackage)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
module Box
|
4
|
+
# Action to destroy a box. This action is not reversible and expects
|
5
|
+
# to be called by a {Box} object.
|
6
|
+
class Destroy < Base
|
7
|
+
def execute!
|
8
|
+
logger.info "Deleting box directory..."
|
9
|
+
FileUtils.rm_rf(@runner.directory)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
module Box
|
4
|
+
# An action which acts on a box by downloading the box file from
|
5
|
+
# the given URI into a temporary location. This action parses a
|
6
|
+
# given URI and handles downloading it via one of the many Vagrant
|
7
|
+
# downloads (such as {Vagrant::Downloaders::File}).
|
8
|
+
#
|
9
|
+
# This action cleans itself up by removing the downloaded box file.
|
10
|
+
class Download < Base
|
11
|
+
BASENAME = "box"
|
12
|
+
BUFFERSIZE = 1048576 # 1 MB
|
13
|
+
|
14
|
+
attr_reader :downloader
|
15
|
+
|
16
|
+
def prepare
|
17
|
+
# Parse the URI given and prepare a downloader
|
18
|
+
uri = URI.parse(@runner.uri)
|
19
|
+
uri_map = [[URI::HTTP, Downloaders::HTTP], [URI::Generic, Downloaders::File]]
|
20
|
+
|
21
|
+
uri_map.find do |uri_type, downloader_klass|
|
22
|
+
if uri.is_a?(uri_type)
|
23
|
+
logger.info "#{uri_type} for URI, downloading via #{downloader_klass}..."
|
24
|
+
@downloader = downloader_klass.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
raise ActionException.new("Unknown URI type for box download.") unless @downloader
|
29
|
+
end
|
30
|
+
|
31
|
+
def execute!
|
32
|
+
with_tempfile do |tempfile|
|
33
|
+
download_to(tempfile)
|
34
|
+
@runner.temp_path = tempfile.path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cleanup
|
39
|
+
if @runner.temp_path && File.exist?(@runner.temp_path)
|
40
|
+
logger.info "Cleaning up downloaded box..."
|
41
|
+
File.unlink(@runner.temp_path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def rescue(exception)
|
46
|
+
cleanup
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_tempfile
|
50
|
+
logger.info "Creating tempfile for storing box file..."
|
51
|
+
Tempfile.open(BASENAME, Env.tmp_path) do |tempfile|
|
52
|
+
yield tempfile
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def download_to(f)
|
57
|
+
logger.info "Copying box to temporary location..."
|
58
|
+
downloader.download!(@runner.uri, f)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
module Box
|
4
|
+
# This action unpackages a downloaded box file into its final
|
5
|
+
# box destination within the vagrant home folder.
|
6
|
+
class Unpackage < Base
|
7
|
+
TAR_OPTIONS = [File::RDONLY, 0644, Tar::GNU]
|
8
|
+
|
9
|
+
def execute!
|
10
|
+
@runner.invoke_around_callback(:unpackage) do
|
11
|
+
setup_box_dir
|
12
|
+
decompress
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def rescue(exception)
|
17
|
+
if File.directory?(box_dir)
|
18
|
+
logger.info "An error occurred, rolling back box unpackaging..."
|
19
|
+
FileUtils.rm_rf(box_dir)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_box_dir
|
24
|
+
if File.directory?(box_dir)
|
25
|
+
error_and_exit(<<-msg)
|
26
|
+
This box appears to already exist! Please call `vagrant box remove #{@runner.name}`
|
27
|
+
and then try to add it again.
|
28
|
+
msg
|
29
|
+
end
|
30
|
+
|
31
|
+
FileUtils.mkdir_p(box_dir)
|
32
|
+
end
|
33
|
+
|
34
|
+
def box_dir
|
35
|
+
@runner.directory
|
36
|
+
end
|
37
|
+
|
38
|
+
def decompress
|
39
|
+
Dir.chdir(box_dir) do
|
40
|
+
logger.info "Extracting box to #{box_dir}..."
|
41
|
+
Tar.open(@runner.temp_path, *TAR_OPTIONS) do |tar|
|
42
|
+
tar.extract_all
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
# Base class for any class which will act as a runner
|
4
|
+
# for actions. A runner handles queueing up and executing actions,
|
5
|
+
# and executing the methods of an action in the proper order. The
|
6
|
+
# action runner also handles invoking callbacks that actions may
|
7
|
+
# request.
|
8
|
+
#
|
9
|
+
# # Executing Actions
|
10
|
+
#
|
11
|
+
# Actions can be executed by adding them and executing them all
|
12
|
+
# at once:
|
13
|
+
#
|
14
|
+
# runner = Vagrant::Actions::Runner.new
|
15
|
+
# runner.add_action(FooAction)
|
16
|
+
# runner.add_action(BarAction)
|
17
|
+
# runner.add_action(BazAction)
|
18
|
+
# runner.execute!
|
19
|
+
#
|
20
|
+
# Single actions have a shorthand to be executed:
|
21
|
+
#
|
22
|
+
# Vagrant::Actions::Runner.execute!(FooAction)
|
23
|
+
#
|
24
|
+
# Arguments may be passed into added actions by adding them after
|
25
|
+
# the action class:
|
26
|
+
#
|
27
|
+
# runner.add_action(FooAction, "many", "arguments", "may", "follow")
|
28
|
+
#
|
29
|
+
class Runner
|
30
|
+
include Vagrant::Util
|
31
|
+
|
32
|
+
class << self
|
33
|
+
# Executes a specific action, optionally passing in any arguments to that
|
34
|
+
# action. This method is shorthand to initializing a runner, adding a single
|
35
|
+
# action, and executing it.
|
36
|
+
def execute!(action_klass, *args)
|
37
|
+
runner = new
|
38
|
+
runner.add_action(action_klass, *args)
|
39
|
+
runner.execute!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns an array of all the actions in queue. Because this
|
44
|
+
# will persist accross calls (calling {#actions} twice will yield
|
45
|
+
# exactly the same object), to clear or modify it, use the ruby
|
46
|
+
# array methods which act on `self`, such as `Array#clear`.
|
47
|
+
#
|
48
|
+
# @return [Array]
|
49
|
+
def actions
|
50
|
+
@actions ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the first action instance which matches the given class.
|
54
|
+
#
|
55
|
+
# @param [Class] action_klass The action to search for in the queue
|
56
|
+
# @return [Object]
|
57
|
+
def find_action(action_klass)
|
58
|
+
actions.find { |a| a.is_a?(action_klass) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Add an action to the list of queued actions to execute. This method
|
62
|
+
# appends the given action class to the end of the queue. Any arguments
|
63
|
+
# given after the class are passed into the class constructor.
|
64
|
+
def add_action(action_klass, *args)
|
65
|
+
actions << action_klass.new(self, *args)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Execute the actions in queue. This method can also optionally be used
|
69
|
+
# to execute a single action on an instance. The syntax for executing a
|
70
|
+
# single method on an instance is the same as the {execute!} class method.
|
71
|
+
def execute!(single_action=nil, *args)
|
72
|
+
if single_action
|
73
|
+
actions.clear
|
74
|
+
add_action(single_action, *args)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Call the prepare method on each once its
|
78
|
+
# initialized, then call the execute! method
|
79
|
+
begin
|
80
|
+
[:prepare, :execute!, :cleanup].each do |method|
|
81
|
+
actions.each do |action|
|
82
|
+
action.send(method)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
rescue Exception => e
|
86
|
+
# Run the rescue code to do any emergency cleanup
|
87
|
+
actions.each do |action|
|
88
|
+
action.rescue(e)
|
89
|
+
end
|
90
|
+
|
91
|
+
# If its an ActionException, error and exit the message
|
92
|
+
if e.is_a?(ActionException)
|
93
|
+
error_and_exit(e.message)
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
97
|
+
# Finally, reraise the exception
|
98
|
+
raise
|
99
|
+
end
|
100
|
+
|
101
|
+
# Clear the actions
|
102
|
+
actions.clear
|
103
|
+
end
|
104
|
+
|
105
|
+
# Invokes an "around callback" which invokes before_name and
|
106
|
+
# after_name for the given callback name, yielding a block between
|
107
|
+
# callback invokations.
|
108
|
+
def invoke_around_callback(name, *args)
|
109
|
+
invoke_callback("before_#{name}".to_sym, *args)
|
110
|
+
yield
|
111
|
+
invoke_callback("after_#{name}".to_sym, *args)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Invokes a single callback. This method will go through each action
|
115
|
+
# and call the method given in the parameter `name` if the action
|
116
|
+
# responds to it.
|
117
|
+
def invoke_callback(name, *args)
|
118
|
+
# Attempt to call the method for the callback on each of the
|
119
|
+
# actions
|
120
|
+
results = []
|
121
|
+
actions.each do |action|
|
122
|
+
results << action.send(name, *args) if action.respond_to?(name)
|
123
|
+
end
|
124
|
+
results
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module Actions
|
3
|
+
module VM
|
4
|
+
class Destroy < Base
|
5
|
+
def execute!
|
6
|
+
@runner.invoke_around_callback(:destroy) do
|
7
|
+
logger.info "Destroying VM and associated drives..."
|
8
|
+
@runner.vm.destroy(:destroy_image => true)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|