summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider/zone/solaris.rb
blob: 194af5049b982e6eb72f3c29ccdd3977f1d25cb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
Puppet::Type.type(:zone).provide(:solaris) do
  desc "Provider for Solaris Zones."

  commands :adm => "/usr/sbin/zoneadm", :cfg => "/usr/sbin/zonecfg"
  defaultfor :operatingsystem => :solaris

  mk_resource_methods

  # Convert the output of a list into a hash
  def self.line2hash(line)
    fields = [:id, :name, :ensure, :path]

    properties = {}
    line.split(":").each_with_index { |value, index|
      next unless fields[index]
      properties[fields[index]] = value
    }

    # Configured but not installed zones do not have IDs
    properties.delete(:id) if properties[:id] == "-"

    properties[:ensure] = symbolize(properties[:ensure])

    properties
  end

  def self.instances
    # LAK:NOTE See http://snurl.com/21zf8  [groups_google_com]
    x = adm(:list, "-cp").split("\n").collect do |line|
      new(line2hash(line))
    end
  end

  # Perform all of our configuration steps.
  def configure
    # If the thing is entirely absent, then we need to create the config.
    # Is there someway to get this on one line?
    str = "create -b #{@resource[:create_args]}\nset zonepath=#{@resource[:path]}\n"

    # Then perform all of our configuration steps.  It's annoying
    # that we need this much internal info on the resource.
    @resource.send(:properties).each do |property|
      str += property.configtext + "\n" if property.is_a? ZoneConfigProperty and ! property.safe_insync?(properties[property.name])
    end

    str += "commit\n"
    setconfig(str)
  end

  def destroy
    zonecfg :delete, "-F"
  end

  def exists?
    properties[:ensure] != :absent
  end

  # Clear out the cached values.
  def flush
    @property_hash.clear
  end

  def install(dummy_argument=:work_arround_for_ruby_GC_bug)
    if @resource[:clone] # TODO: add support for "-s snapshot"
      zoneadm :clone, @resource[:clone]
    elsif @resource[:install_args]
      zoneadm :install, @resource[:install_args].split(" ")
    else
      zoneadm :install
    end
  end

  # Look up the current status.
  def properties
    if @property_hash.empty?
      @property_hash = status || {}
      if @property_hash.empty?
        @property_hash[:ensure] = :absent
      else
        @resource.class.validproperties.each do |name|
          @property_hash[name] ||= :absent
        end
      end

    end
    @property_hash.dup
  end

  # We need a way to test whether a zone is in process.  Our 'ensure'
  # property models the static states, but we need to handle the temporary ones.
  def processing?
    if hash = status
      case hash[:ensure]
      when "incomplete", "ready", "shutting_down"
        true
      else
        false
      end
    else
      false
    end
  end

  # Collect the configuration of the zone.
  def getconfig
    output = zonecfg :info

    name = nil
    current = nil
    hash = {}
    output.split("\n").each do |line|
      case line
      when /^(\S+):\s*$/
        name = $1
        current = nil # reset it
      when /^(\S+):\s*(.+)$/
        hash[$1.intern] = $2
      when /^\s+(\S+):\s*(.+)$/
        if name
          hash[name] = [] unless hash.include? name

          unless current
            current = {}
            hash[name] << current
          end
          current[$1.intern] = $2
        else
          err "Ignoring '#{line}'"
        end
      else
        debug "Ignoring zone output '#{line}'"
      end
    end

    hash
  end

  # Execute a configuration string.  Can't be private because it's called
  # by the properties.
  def setconfig(str)
    command = "#{command(:cfg)} -z #{@resource[:name]} -f -"
    debug "Executing '#{command}' in zone #{@resource[:name]} with '#{str}'"
    IO.popen(command, "w") do |pipe|
      pipe.puts str
    end

    unless $CHILD_STATUS == 0
      raise ArgumentError, "Failed to apply configuration"
    end
  end

  def start
    # Check the sysidcfg stuff
    if cfg = @resource[:sysidcfg]
      zoneetc = File.join(@resource[:path], "root", "etc")
      sysidcfg = File.join(zoneetc, "sysidcfg")

      # if the zone root isn't present "ready" the zone
      # which makes zoneadmd mount the zone root
      zoneadm :ready unless File.directory?(zoneetc)

      unless File.exists?(sysidcfg)
        begin
          File.open(sysidcfg, "w", 0600) do |f|
            f.puts cfg
          end
        rescue => detail
          puts detail.stacktrace if Puppet[:debug]
          raise Puppet::Error, "Could not create sysidcfg: #{detail}"
        end
      end
    end

    zoneadm :boot
  end

  # Return a hash of the current status of this zone.
  def status
    begin
      output = adm "-z", @resource[:name], :list, "-p"
    rescue Puppet::ExecutionFailure
      return nil
    end

    main = self.class.line2hash(output.chomp)

    # Now add in the configuration information
    config_status.each do |name, value|
      main[name] = value
    end

    main
  end

  def ready
    zoneadm :ready
  end

  def stop
    zoneadm :halt
  end

  def unconfigure
    zonecfg :delete, "-F"
  end

  def uninstall
    zoneadm :uninstall, "-F"
  end

  private

  # Turn the results of getconfig into status information.
  def config_status
    config = getconfig
    result = {}

    result[:autoboot] = config[:autoboot] ? config[:autoboot].intern : :absent
    result[:pool] = config[:pool]
    result[:shares] = config[:shares]
    if dir = config["inherit-pkg-dir"]
      result[:inherit] = dir.collect { |dirs| dirs[:dir] }
    end
    if datasets = config["dataset"]
      result[:dataset] = datasets.collect { |dataset| dataset[:name] }
    end
    result[:iptype] = config[:"ip-type"]
    if net = config["net"]
      result[:ip] = net.collect do |params|
        if params[:defrouter]
          "#{params[:physical]}:#{params[:address]}:#{params[:defrouter]}"
        elsif params[:address]
          "#{params[:physical]}:#{params[:address]}"
        else
          params[:physical]
        end
      end
    end

    result
  end

  def zoneadm(*cmd)
      adm("-z", @resource[:name], *cmd)
  rescue Puppet::ExecutionFailure => detail
      self.fail "Could not #{cmd[0]} zone: #{detail}"
  end

  def zonecfg(*cmd)
    # You apparently can't get the configuration of the global zone
    return "" if self.name == "global"

    begin
      cfg("-z", self.name, *cmd)
    rescue Puppet::ExecutionFailure => detail
      self.fail "Could not #{cmd[0]} zone: #{detail}"
    end
  end
end