backup-bouchard 4.4.1

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.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +29 -0
  4. data/bin/backup +5 -0
  5. data/lib/backup.rb +140 -0
  6. data/lib/backup/archive.rb +169 -0
  7. data/lib/backup/binder.rb +18 -0
  8. data/lib/backup/cleaner.rb +112 -0
  9. data/lib/backup/cli.rb +370 -0
  10. data/lib/backup/cloud_io/base.rb +38 -0
  11. data/lib/backup/cloud_io/cloud_files.rb +296 -0
  12. data/lib/backup/cloud_io/s3.rb +253 -0
  13. data/lib/backup/compressor/base.rb +32 -0
  14. data/lib/backup/compressor/bzip2.rb +35 -0
  15. data/lib/backup/compressor/custom.rb +49 -0
  16. data/lib/backup/compressor/gzip.rb +73 -0
  17. data/lib/backup/config.rb +118 -0
  18. data/lib/backup/config/dsl.rb +100 -0
  19. data/lib/backup/config/helpers.rb +137 -0
  20. data/lib/backup/database/base.rb +86 -0
  21. data/lib/backup/database/mongodb.rb +187 -0
  22. data/lib/backup/database/mysql.rb +191 -0
  23. data/lib/backup/database/openldap.rb +93 -0
  24. data/lib/backup/database/postgresql.rb +132 -0
  25. data/lib/backup/database/redis.rb +177 -0
  26. data/lib/backup/database/riak.rb +79 -0
  27. data/lib/backup/database/sqlite.rb +55 -0
  28. data/lib/backup/encryptor/base.rb +27 -0
  29. data/lib/backup/encryptor/gpg.rb +740 -0
  30. data/lib/backup/encryptor/open_ssl.rb +74 -0
  31. data/lib/backup/errors.rb +53 -0
  32. data/lib/backup/logger.rb +197 -0
  33. data/lib/backup/logger/console.rb +48 -0
  34. data/lib/backup/logger/fog_adapter.rb +25 -0
  35. data/lib/backup/logger/logfile.rb +131 -0
  36. data/lib/backup/logger/syslog.rb +114 -0
  37. data/lib/backup/model.rb +477 -0
  38. data/lib/backup/notifier/base.rb +126 -0
  39. data/lib/backup/notifier/campfire.rb +61 -0
  40. data/lib/backup/notifier/command.rb +99 -0
  41. data/lib/backup/notifier/datadog.rb +104 -0
  42. data/lib/backup/notifier/flowdock.rb +99 -0
  43. data/lib/backup/notifier/hipchat.rb +116 -0
  44. data/lib/backup/notifier/http_post.rb +114 -0
  45. data/lib/backup/notifier/mail.rb +246 -0
  46. data/lib/backup/notifier/nagios.rb +65 -0
  47. data/lib/backup/notifier/pagerduty.rb +79 -0
  48. data/lib/backup/notifier/prowl.rb +68 -0
  49. data/lib/backup/notifier/pushover.rb +71 -0
  50. data/lib/backup/notifier/ses.rb +103 -0
  51. data/lib/backup/notifier/slack.rb +147 -0
  52. data/lib/backup/notifier/twitter.rb +55 -0
  53. data/lib/backup/notifier/zabbix.rb +60 -0
  54. data/lib/backup/package.rb +51 -0
  55. data/lib/backup/packager.rb +105 -0
  56. data/lib/backup/pipeline.rb +120 -0
  57. data/lib/backup/splitter.rb +73 -0
  58. data/lib/backup/storage/base.rb +66 -0
  59. data/lib/backup/storage/cloud_files.rb +156 -0
  60. data/lib/backup/storage/cycler.rb +70 -0
  61. data/lib/backup/storage/dropbox.rb +210 -0
  62. data/lib/backup/storage/ftp.rb +110 -0
  63. data/lib/backup/storage/local.rb +61 -0
  64. data/lib/backup/storage/qiniu.rb +65 -0
  65. data/lib/backup/storage/rsync.rb +246 -0
  66. data/lib/backup/storage/s3.rb +155 -0
  67. data/lib/backup/storage/scp.rb +65 -0
  68. data/lib/backup/storage/sftp.rb +80 -0
  69. data/lib/backup/syncer/base.rb +67 -0
  70. data/lib/backup/syncer/cloud/base.rb +176 -0
  71. data/lib/backup/syncer/cloud/cloud_files.rb +81 -0
  72. data/lib/backup/syncer/cloud/local_file.rb +97 -0
  73. data/lib/backup/syncer/cloud/s3.rb +109 -0
  74. data/lib/backup/syncer/rsync/base.rb +50 -0
  75. data/lib/backup/syncer/rsync/local.rb +27 -0
  76. data/lib/backup/syncer/rsync/pull.rb +47 -0
  77. data/lib/backup/syncer/rsync/push.rb +201 -0
  78. data/lib/backup/template.rb +41 -0
  79. data/lib/backup/utilities.rb +228 -0
  80. data/lib/backup/version.rb +3 -0
  81. data/templates/cli/archive +28 -0
  82. data/templates/cli/compressor/bzip2 +4 -0
  83. data/templates/cli/compressor/custom +7 -0
  84. data/templates/cli/compressor/gzip +4 -0
  85. data/templates/cli/config +123 -0
  86. data/templates/cli/databases/mongodb +15 -0
  87. data/templates/cli/databases/mysql +18 -0
  88. data/templates/cli/databases/openldap +24 -0
  89. data/templates/cli/databases/postgresql +16 -0
  90. data/templates/cli/databases/redis +16 -0
  91. data/templates/cli/databases/riak +17 -0
  92. data/templates/cli/databases/sqlite +11 -0
  93. data/templates/cli/encryptor/gpg +27 -0
  94. data/templates/cli/encryptor/openssl +9 -0
  95. data/templates/cli/model +26 -0
  96. data/templates/cli/notifier/zabbix +15 -0
  97. data/templates/cli/notifiers/campfire +12 -0
  98. data/templates/cli/notifiers/command +32 -0
  99. data/templates/cli/notifiers/datadog +57 -0
  100. data/templates/cli/notifiers/flowdock +16 -0
  101. data/templates/cli/notifiers/hipchat +16 -0
  102. data/templates/cli/notifiers/http_post +32 -0
  103. data/templates/cli/notifiers/mail +24 -0
  104. data/templates/cli/notifiers/nagios +13 -0
  105. data/templates/cli/notifiers/pagerduty +12 -0
  106. data/templates/cli/notifiers/prowl +11 -0
  107. data/templates/cli/notifiers/pushover +11 -0
  108. data/templates/cli/notifiers/ses +15 -0
  109. data/templates/cli/notifiers/slack +22 -0
  110. data/templates/cli/notifiers/twitter +13 -0
  111. data/templates/cli/splitter +7 -0
  112. data/templates/cli/storages/cloud_files +11 -0
  113. data/templates/cli/storages/dropbox +20 -0
  114. data/templates/cli/storages/ftp +13 -0
  115. data/templates/cli/storages/local +8 -0
  116. data/templates/cli/storages/qiniu +12 -0
  117. data/templates/cli/storages/rsync +17 -0
  118. data/templates/cli/storages/s3 +16 -0
  119. data/templates/cli/storages/scp +15 -0
  120. data/templates/cli/storages/sftp +15 -0
  121. data/templates/cli/syncers/cloud_files +22 -0
  122. data/templates/cli/syncers/rsync_local +20 -0
  123. data/templates/cli/syncers/rsync_pull +28 -0
  124. data/templates/cli/syncers/rsync_push +28 -0
  125. data/templates/cli/syncers/s3 +27 -0
  126. data/templates/general/links +3 -0
  127. data/templates/general/version.erb +2 -0
  128. data/templates/notifier/mail/failure.erb +16 -0
  129. data/templates/notifier/mail/success.erb +16 -0
  130. data/templates/notifier/mail/warning.erb +16 -0
  131. data/templates/storage/dropbox/authorization_url.erb +6 -0
  132. data/templates/storage/dropbox/authorized.erb +4 -0
  133. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  134. metadata +518 -0
@@ -0,0 +1,100 @@
1
+ module Backup
2
+ module Config
3
+ # Context for loading user config.rb and model files.
4
+ class DSL
5
+ class Error < Backup::Error; end
6
+ Model = Backup::Model
7
+
8
+ class << self
9
+ private
10
+
11
+ # List the available database, storage, syncer, compressor, encryptor
12
+ # and notifier constants. These are used to define constant names within
13
+ # Backup::Config::DSL so that users may use a constant instead of a string.
14
+ # Nested namespaces are represented using Hashs. Deep nesting supported.
15
+ #
16
+ # Example, instead of:
17
+ # database "MySQL" do |mysql|
18
+ # sync_with "RSync::Local" do |rsync|
19
+ #
20
+ # You can do:
21
+ # database MySQL do |mysql|
22
+ # sync_with RSync::Local do |rsync|
23
+ #
24
+ def add_dsl_constants
25
+ create_modules(
26
+ DSL,
27
+ [ # Databases
28
+ ["MySQL", "PostgreSQL", "MongoDB", "Redis", "Riak", "OpenLDAP", "SQLite"],
29
+ # Storages
30
+ ["S3", "CloudFiles", "Ninefold", "Dropbox", "FTP",
31
+ "SFTP", "SCP", "RSync", "Local", "Qiniu"],
32
+ # Compressors
33
+ ["Gzip", "Bzip2", "Custom", "Pbzip2", "Lzma"],
34
+ # Encryptors
35
+ ["OpenSSL", "GPG"],
36
+ # Syncers
37
+ [
38
+ { "Cloud" => ["CloudFiles", "S3"] },
39
+ { "RSync" => ["Push", "Pull", "Local"] }
40
+ ],
41
+ # Notifiers
42
+ ["Mail", "Twitter", "Campfire", "Prowl",
43
+ "Hipchat", "PagerDuty", "Pushover", "HttpPost", "Nagios",
44
+ "Slack", "FlowDock", "Zabbix", "Ses", "DataDog", "Command"]
45
+ ]
46
+ )
47
+ end
48
+
49
+ def create_modules(scope, names)
50
+ names.flatten.each do |name|
51
+ if name.is_a?(Hash)
52
+ name.each do |key, val|
53
+ create_modules(get_or_create_empty_module(scope, key), [val])
54
+ end
55
+ else
56
+ get_or_create_empty_module(scope, name)
57
+ end
58
+ end
59
+ end
60
+
61
+ def get_or_create_empty_module(scope, const)
62
+ if scope.const_defined?(const)
63
+ scope.const_get(const)
64
+ else
65
+ scope.const_set(const, Module.new)
66
+ end
67
+ end
68
+ end
69
+
70
+ add_dsl_constants # add constants on load
71
+
72
+ attr_reader :_config_options
73
+
74
+ def initialize
75
+ @_config_options = {}
76
+ end
77
+
78
+ # Allow users to set command line path options in config.rb
79
+ [:root_path, :data_path, :tmp_path].each do |name|
80
+ define_method name, ->(path) { _config_options[name] = path }
81
+ end
82
+
83
+ # Allows users to create preconfigured models.
84
+ def preconfigure(name, &block)
85
+ unless name.is_a?(String) && name =~ /^[A-Z]/
86
+ raise Error, "Preconfigured model names must be given as a string " \
87
+ "and start with a capital letter."
88
+ end
89
+
90
+ if DSL.const_defined?(name)
91
+ raise Error, "'#{name}' is already in use " \
92
+ "and can not be used for a preconfigured model."
93
+ end
94
+
95
+ DSL.const_set(name, Class.new(Model))
96
+ DSL.const_get(name).preconfigure(&block)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,137 @@
1
+ require "ostruct"
2
+
3
+ module Backup
4
+ module Config
5
+ module Helpers
6
+ def self.included(klass)
7
+ klass.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def defaults
12
+ @defaults ||= Config::Defaults.new
13
+
14
+ if block_given?
15
+ yield @defaults
16
+ else
17
+ @defaults
18
+ end
19
+ end
20
+
21
+ # Used only within the specs
22
+ def clear_defaults!
23
+ defaults.reset!
24
+ end
25
+
26
+ def deprecations
27
+ @deprecations ||= {}
28
+ end
29
+
30
+ def log_deprecation_warning(name, deprecation)
31
+ msg = "#{self}##{name} has been deprecated as of " \
32
+ "backup v.#{deprecation[:version]}"
33
+ msg << "\n#{deprecation[:message]}" if deprecation[:message]
34
+ Logger.warn Config::Error.new(<<-EOS)
35
+ [DEPRECATION WARNING]
36
+ #{msg}
37
+ EOS
38
+ end
39
+
40
+ protected
41
+
42
+ ##
43
+ # Method to deprecate an attribute.
44
+ #
45
+ # :version
46
+ # Must be set to the backup version which will first
47
+ # introduce the deprecation.
48
+ #
49
+ # :action
50
+ # If set, this Proc will be called with a reference to the
51
+ # class instance and the value set on the deprecated accessor.
52
+ # e.g. deprecation[:action].call(klass, value)
53
+ # This should perform whatever action is neccessary, such as
54
+ # transferring the value to a new accessor.
55
+ #
56
+ # :message
57
+ # If set, this will be appended to #log_deprecation_warning
58
+ #
59
+ # Note that this replaces the `attr_accessor` method, or other
60
+ # method previously used to set the accessor being deprecated.
61
+ # #method_missing will handle any calls to `name=`.
62
+ #
63
+ def attr_deprecate(name, args = {})
64
+ deprecations[name] = {
65
+ version: nil,
66
+ message: nil,
67
+ action: nil
68
+ }.merge(args)
69
+ end
70
+ end # ClassMethods
71
+
72
+ private
73
+
74
+ ##
75
+ # Sets any pre-configured default values.
76
+ # If a default value was set for an invalid accessor,
77
+ # this will raise a NameError.
78
+ def load_defaults!
79
+ self.class.defaults._attributes.each do |name|
80
+ val = self.class.defaults.send(name)
81
+ val = val.dup rescue val
82
+ send(:"#{ name }=", val)
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Check missing methods for deprecated attribute accessors.
88
+ #
89
+ # If a value is set on an accessor that has been deprecated
90
+ # using #attr_deprecate, a warning will be issued and any
91
+ # :action (Proc) specified will be called with a reference to
92
+ # the class instance and the value set on the deprecated accessor.
93
+ # See #attr_deprecate and #log_deprecation_warning
94
+ #
95
+ # Note that OpenStruct (used for setting defaults) does not allow
96
+ # multiple arguments when assigning values for members.
97
+ # So, we won't allow it here either, even though an attr_accessor
98
+ # will accept and convert them into an Array. Therefore, setting
99
+ # an option value using multiple values, whether as a default or
100
+ # directly on the class' accessor, should not be supported.
101
+ # i.e. if an option will accept being set as an Array, then it
102
+ # should be explicitly set as such. e.g. option = [val1, val2]
103
+ #
104
+ def method_missing(name, *args)
105
+ deprecation = nil
106
+ if method = name.to_s.chomp!("=")
107
+ if (len = args.count) != 1
108
+ raise ArgumentError,
109
+ "wrong number of arguments (#{len} for 1)", caller(1)
110
+ end
111
+ deprecation = self.class.deprecations[method.to_sym]
112
+ end
113
+
114
+ if deprecation
115
+ self.class.log_deprecation_warning(method, deprecation)
116
+ deprecation[:action].call(self, args[0]) if deprecation[:action]
117
+ else
118
+ super
119
+ end
120
+ end
121
+ end # Helpers
122
+
123
+ # Store for pre-configured defaults.
124
+ class Defaults < OpenStruct
125
+ # Returns an Array of all attribute method names
126
+ # that default values were set for.
127
+ def _attributes
128
+ @table.keys
129
+ end
130
+
131
+ # Used only within the specs
132
+ def reset!
133
+ @table.clear
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,86 @@
1
+ module Backup
2
+ module Database
3
+ class Error < Backup::Error; end
4
+
5
+ class Base
6
+ include Utilities::Helpers
7
+ include Config::Helpers
8
+
9
+ attr_reader :model, :database_id, :dump_path
10
+
11
+ ##
12
+ # If given, +database_id+ will be appended to the #dump_filename.
13
+ # This is required if multiple Databases of the same class are added to
14
+ # the model.
15
+ def initialize(model, database_id = nil)
16
+ @model = model
17
+ @database_id = database_id.to_s.gsub(/\W/, "_") if database_id
18
+ @dump_path = File.join(Config.tmp_path, model.trigger, "databases")
19
+ load_defaults!
20
+ end
21
+
22
+ def perform!
23
+ log!(:started)
24
+ prepare!
25
+ end
26
+
27
+ private
28
+
29
+ def prepare!
30
+ FileUtils.mkdir_p(dump_path)
31
+ end
32
+
33
+ ##
34
+ # Sets the base filename for the final dump file to be saved in +dump_path+,
35
+ # based on the class name. e.g. databases/MySQL.sql
36
+ #
37
+ # +database_id+ will be appended if it is defined.
38
+ # e.g. databases/MySQL-database_id.sql
39
+ #
40
+ # If multiple Databases of the same class are defined and no +database_id+
41
+ # is defined, the user will be warned and one will be auto-generated.
42
+ #
43
+ # Model#initialize calls this method *after* all defined databases have
44
+ # been initialized so `backup check` can report these warnings.
45
+ def dump_filename
46
+ @dump_filename ||=
47
+ begin
48
+ unless database_id
49
+ if model.databases.select { |d| d.class == self.class }.count > 1
50
+ sleep 1
51
+ @database_id = Time.now.to_i.to_s[-5, 5]
52
+ Logger.warn Error.new(<<-EOS)
53
+ Database Identifier Missing
54
+ When multiple Databases are configured in a single Backup Model
55
+ that have the same class (MySQL, PostgreSQL, etc.), the optional
56
+ +database_id+ must be specified to uniquely identify each instance.
57
+ e.g. database MySQL, :database_id do |db|
58
+ This will result in an output file in your final backup package like:
59
+ databases/MySQL-database_id.sql
60
+
61
+ Backup has auto-generated an identifier (#{database_id}) for this
62
+ database dump and will now continue.
63
+ EOS
64
+ end
65
+ end
66
+
67
+ self.class.name.split("::").last + (database_id ? "-#{database_id}" : "")
68
+ end
69
+ end
70
+
71
+ def database_name
72
+ @database_name ||= self.class.to_s.sub("Backup::", "") +
73
+ (database_id ? " (#{database_id})" : "")
74
+ end
75
+
76
+ def log!(action)
77
+ msg =
78
+ case action
79
+ when :started then "Started..."
80
+ when :finished then "Finished!"
81
+ end
82
+ Logger.info "#{database_name} #{msg}"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,187 @@
1
+ module Backup
2
+ module Database
3
+ class MongoDB < Base
4
+ class Error < Backup::Error; end
5
+
6
+ ##
7
+ # Name of the database that needs to get dumped
8
+ attr_accessor :name
9
+
10
+ ##
11
+ # Credentials for the specified database
12
+ attr_accessor :username, :password, :authdb
13
+
14
+ ##
15
+ # Connectivity options
16
+ attr_accessor :host, :port
17
+
18
+ ##
19
+ # IPv6 support (disabled by default)
20
+ attr_accessor :ipv6
21
+
22
+ ##
23
+ # Collections to dump, collections that aren't specified won't get dumped
24
+ attr_accessor :only_collections
25
+
26
+ ##
27
+ # Additional "mongodump" options
28
+ attr_accessor :additional_options
29
+
30
+ ##
31
+ # Forces mongod to flush all pending write operations to the disk and
32
+ # locks the entire mongod instance to prevent additional writes until the
33
+ # dump is complete.
34
+ #
35
+ # Note that if Profiling is enabled, this will disable it and will not
36
+ # re-enable it after the dump is complete.
37
+ attr_accessor :lock
38
+
39
+ ##
40
+ # Creates a dump of the database that includes an oplog, to create a
41
+ # point-in-time snapshot of the state of a mongod instance.
42
+ #
43
+ # If this option is used, you would not use the `lock` option.
44
+ #
45
+ # This will only work against nodes that maintain a oplog.
46
+ # This includes all members of a replica set, as well as master nodes in
47
+ # master/slave replication deployments.
48
+ attr_accessor :oplog
49
+
50
+ def initialize(model, database_id = nil, &block)
51
+ super
52
+ instance_eval(&block) if block_given?
53
+ end
54
+
55
+ def perform!
56
+ super
57
+
58
+ lock_database if @lock
59
+ dump!
60
+ package!
61
+
62
+ ensure
63
+ unlock_database if @lock
64
+ end
65
+
66
+ private
67
+
68
+ ##
69
+ # Performs all required mongodump commands, dumping the output files
70
+ # into the +dump_packaging_path+ directory for packaging.
71
+ def dump!
72
+ FileUtils.mkdir_p dump_packaging_path
73
+
74
+ collections = Array(only_collections)
75
+ if collections.empty?
76
+ run(mongodump)
77
+ else
78
+ collections.each do |collection|
79
+ run("#{mongodump} --collection='#{collection}'")
80
+ end
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Creates a tar archive of the +dump_packaging_path+ directory
86
+ # and stores it in the +dump_path+ using +dump_filename+.
87
+ #
88
+ # <trigger>/databases/MongoDB[-<database_id>].tar[.gz]
89
+ #
90
+ # If successful, +dump_packaging_path+ is removed.
91
+ def package!
92
+ pipeline = Pipeline.new
93
+ dump_ext = "tar"
94
+
95
+ pipeline << "#{utility(:tar)} -cf - " \
96
+ "-C '#{dump_path}' '#{dump_filename}'"
97
+
98
+ if model.compressor
99
+ model.compressor.compress_with do |command, ext|
100
+ pipeline << command
101
+ dump_ext << ext
102
+ end
103
+ end
104
+
105
+ pipeline << "#{utility(:cat)} > " \
106
+ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
107
+
108
+ pipeline.run
109
+ if pipeline.success?
110
+ FileUtils.rm_rf dump_packaging_path
111
+ log!(:finished)
112
+ else
113
+ raise Error, "Dump Failed!\n#{pipeline.error_messages}"
114
+ end
115
+ end
116
+
117
+ def dump_packaging_path
118
+ File.join(dump_path, dump_filename)
119
+ end
120
+
121
+ def mongodump
122
+ "#{utility(:mongodump)} #{name_option} #{credential_options} " \
123
+ "#{connectivity_options} #{ipv6_option} #{oplog_option} " \
124
+ "#{user_options} --out='#{dump_packaging_path}'"
125
+ end
126
+
127
+ def name_option
128
+ return unless name
129
+ "--db='#{name}'"
130
+ end
131
+
132
+ def credential_options
133
+ opts = []
134
+ opts << "--username='#{username}'" if username
135
+ opts << "--password='#{password}'" if password
136
+ opts << "--authenticationDatabase='#{authdb}'" if authdb
137
+ opts.join(" ")
138
+ end
139
+
140
+ def connectivity_options
141
+ opts = []
142
+ opts << "--host='#{host}'" if host
143
+ opts << "--port='#{port}'" if port
144
+ opts.join(" ")
145
+ end
146
+
147
+ def ipv6_option
148
+ "--ipv6" if ipv6
149
+ end
150
+
151
+ def oplog_option
152
+ "--oplog" if oplog
153
+ end
154
+
155
+ def user_options
156
+ Array(additional_options).join(" ")
157
+ end
158
+
159
+ def lock_database
160
+ lock_command = <<-EOS.gsub(/^ +/, "")
161
+ echo 'use admin
162
+ db.setProfilingLevel(0)
163
+ db.fsyncLock()' | #{mongo_shell}
164
+ EOS
165
+
166
+ run(lock_command)
167
+ end
168
+
169
+ def unlock_database
170
+ unlock_command = <<-EOS.gsub(/^ +/, "")
171
+ echo 'use admin
172
+ db.fsyncUnlock()' | #{mongo_shell}
173
+ EOS
174
+
175
+ run(unlock_command)
176
+ end
177
+
178
+ def mongo_shell
179
+ cmd = "#{utility(:mongo)} #{connectivity_options}".rstrip
180
+ cmd << " #{credential_options}".rstrip
181
+ cmd << " #{ipv6_option}".rstrip
182
+ cmd << " '#{name}'" if name
183
+ cmd
184
+ end
185
+ end
186
+ end
187
+ end