summaryrefslogtreecommitdiff
path: root/lib/chef/data_bag_item.rb
blob: 7a7e7afdcabdbd5b2809cba9390b969ad584b640 (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
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
# Copyright:: Copyright 2009-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# 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.
#

require "forwardable"

require "chef/config"
require "chef/mixin/params_validate"
require "chef/mixin/from_file"
require "chef/data_bag"
require "chef/mash"
require "chef/server_api"
require "chef/json_compat"

class Chef
  class DataBagItem
    extend Forwardable

    include Chef::Mixin::FromFile
    include Chef::Mixin::ParamsValidate

    VALID_ID = /^[\.\-[:alnum:]_]+$/

    attr_accessor :chef_server_rest

    def self.validate_id!(id_str)
      if id_str.nil? || ( id_str !~ VALID_ID )
        raise Exceptions::InvalidDataBagItemID, "Data Bag items must have an id matching #{VALID_ID.inspect}, you gave: #{id_str.inspect}"
      end
    end

    # Define all Hash's instance methods as delegating to @raw_data
    def_delegators(:@raw_data, *(Hash.instance_methods - Object.instance_methods))

    attr_reader :raw_data

    # Create a new Chef::DataBagItem
    def initialize(chef_server_rest: nil)
      @data_bag = nil
      @raw_data = Mash.new
      @chef_server_rest = chef_server_rest
    end

    def chef_server_rest
      @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url])
    end

    def self.chef_server_rest
      Chef::ServerAPI.new(Chef::Config[:chef_server_url])
    end

    def raw_data
      @raw_data
    end

    def validate_id!(id_str)
      self.class.validate_id!(id_str)
    end

    def raw_data=(new_data)
      unless new_data.respond_to?(:[]) && new_data.respond_to?(:keys)
        raise Exceptions::ValidationFailed, "Data Bag Items must contain a Hash or Mash!"
      end
      validate_id!(new_data["id"])
      @raw_data = new_data
    end

    def data_bag(arg = nil)
      set_or_return(
        :data_bag,
        arg,
        :regex => /^[\-[:alnum:]_]+$/,
      )
    end

    def name
      object_name
    end

    def object_name
      raise Exceptions::ValidationFailed, "You must have an 'id' or :id key in the raw data" unless raw_data.has_key?("id")
      raise Exceptions::ValidationFailed, "You must have declared what bag this item belongs to!" unless data_bag

      id = raw_data["id"]
      "data_bag_item_#{data_bag}_#{id}"
    end

    def self.object_name(data_bag_name, id)
      "data_bag_item_#{data_bag_name}_#{id}"
    end

    def to_hash
      result = self.raw_data
      result["chef_type"] = "data_bag_item"
      result["data_bag"] = self.data_bag
      result
    end

    # Serialize this object as a hash
    def to_json(*a)
      result = {
        "name"       => object_name,
        "json_class" => self.class.name,
        "chef_type"  => "data_bag_item",
        "data_bag"   => data_bag,
        "raw_data"   => raw_data,
      }
      Chef::JSONCompat.to_json(result, *a)
    end

    def self.from_hash(h)
      h.delete("chef_type")
      h.delete("json_class")
      h.delete("name")

      item = new
      item.data_bag(h.delete("data_bag")) if h.key?("data_bag")
      if h.key?("raw_data")
        item.raw_data = Mash.new(h["raw_data"])
      else
        item.raw_data = h
      end
      item
    end

    # Create a Chef::DataBagItem from JSON
    def self.json_create(o)
      Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::DataBagItem#from_hash")
      from_hash(o)
    end

    # Load a Data Bag Item by name via either the RESTful API or local data_bag_path if run in solo mode
    def self.load(data_bag, name)
      if Chef::Config[:solo]
        bag = Chef::DataBag.load(data_bag)
        item = bag[name]
      else
        item = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{data_bag}/#{name}")
      end

      if item.kind_of?(DataBagItem)
        item
      else
        item = from_hash(item)
        item.data_bag(data_bag)
        item
      end
    end

    def destroy(data_bag = self.data_bag(), databag_item = name)
      chef_server_rest.delete("data/#{data_bag}/#{databag_item}")
    end

    # Save this Data Bag Item via RESTful API
    def save(item_id = @raw_data["id"])
      r = chef_server_rest
      begin
        if Chef::Config[:why_run]
          Chef::Log.warn("In why-run mode, so NOT performing data bag item save.")
        else
          r.put("data/#{data_bag}/#{item_id}", self)
        end
      rescue Net::HTTPServerException => e
        raise e unless e.response.code == "404"
        r.post("data/#{data_bag}", self)
      end
      self
    end

    # Create this Data Bag Item via RESTful API
    def create
      chef_server_rest.post("data/#{data_bag}", self)
      self
    end

    def ==(other)
      other.respond_to?(:to_hash) &&
        other.respond_to?(:data_bag) &&
        (other.to_hash == to_hash) &&
        (other.data_bag.to_s == data_bag.to_s)
    end

    # As a string
    def to_s
      "data_bag_item[#{id}]"
    end

    def inspect
      "data_bag_item[#{data_bag.inspect}, #{raw_data['id'].inspect}, #{raw_data.inspect}]"
    end

    def pretty_print(pretty_printer)
      pretty_printer.pp({ "data_bag_item('#{data_bag}', '#{id}')" => self.to_hash })
    end

    def id
      @raw_data["id"]
    end
  end
end