summaryrefslogtreecommitdiff
path: root/lib/leap_cli/commands/node.rb
blob: 1dce437e00e8eb2326be0f6a7f1b1929b37481a0 (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
#
# fyi: the `node init` command lives in node_init.rb,
#      but all other `node x` commands live here.
#

autoload :IPAddr, 'ipaddr'

module LeapCli; module Commands

  ##
  ## COMMANDS
  ##

  desc 'Node management'
  command [:node, :n] do |node|
    node.desc 'Create a new configuration file for a node named NAME.'
    node.long_desc ["If specified, the optional argument SEED can be used to seed values in the node configuration file.",
                    "The format is property_name:value.",
                    "For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.",
                    "To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`",
                    "Separeate multiple values for a single property with a comma, like so: `leap node add mynode services:webapp,dns`"].join("\n\n")
    node.arg_name 'NAME [SEED]' # , :optional => false, :multiple => false
    node.command :add do |add|
      add.switch :local, :desc => 'Make a local testing node (by automatically assigning the next available local IP address). Local nodes are run as virtual machines on your computer.', :negatable => false
      add.action do |global_options,options,args|
        # argument sanity checks
        name = args.first
        assert_valid_node_name!(name, options[:local])
        assert_files_missing! [:node_config, name]

        # create and seed new node
        node = Config::Node.new(manager.env)
        if options[:local]
          node['ip_address'] = pick_next_vagrant_ip_address
        end
        seed_node_data_from_cmd_line(node, args[1..-1])
        seed_node_data_from_template(node)
        validate_ip_address(node)
        begin
          node['name'] = name
          json = node.dump_json(:exclude => ['name'])
          write_file!([:node_config, name], json + "\n")
          if file_exists? :ca_cert, :ca_key
            generate_cert_for_node(manager.reload_node!(node))
          end
        rescue LeapCli::ConfigError
          remove_node_files(name)
        end
      end
    end

    node.desc 'Renames a node file, and all its related files.'
    node.arg_name 'OLD_NAME NEW_NAME'
    node.command :mv do |mv|
      mv.action do |global_options,options,args|
        node = get_node_from_args(args, include_disabled: true)
        new_name = args.last
        assert_valid_node_name!(new_name, node.vagrant?)
        ensure_dir [:node_files_dir, new_name]
        Leap::Platform.node_files.each do |path|
          rename_file! [path, node.name], [path, new_name]
        end
        remove_directory! [:node_files_dir, node.name]
        rename_node_facts(node.name, new_name)
      end
    end

    node.desc 'Removes all the files related to the node named NAME.'
    node.arg_name 'NAME' #:optional => false #, :multiple => false
    node.command :rm do |rm|
      rm.action do |global_options,options,args|
        node = get_node_from_args(args, include_disabled: true)
        remove_node_files(node.name)
        if node.vagrant?
          vagrant_command("destroy --force", [node.name])
        end
        remove_node_facts(node.name)
      end
    end
  end

  ##
  ## PUBLIC HELPERS
  ##

  def get_node_from_args(args, options={})
    node_name = args.first
    node = manager.node(node_name)
    if node.nil? && options[:include_disabled]
      node = manager.disabled_node(node_name)
    end
    assert!(node, "Node '#{node_name}' not found.")
    node
  end

  def seed_node_data_from_cmd_line(node, args)
    args.each do |seed|
      key, value = seed.split(':', 2)
      value = format_seed_value(value)
      assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'"
      if key =~ /\./
        key_parts = key.split('.')
        final_key = key_parts.pop
        current_object = node
        key_parts.each do |key_part|
          current_object[key_part] ||= Config::Object.new
          current_object = current_object[key_part]
        end
        current_object[final_key] = value
      else
        node[key] = value
      end
    end
  end

  #
  # load "new node template" information into the `node`, modifying `node`.
  # values in the template will not override existing node values.
  #
  def seed_node_data_from_template(node)
    node.inherit_from!(manager.template('common'))
    [node['services']].flatten.each do |service|
      if service
        template = manager.template(service)
        if template
          node.inherit_from!(template)
        end
      end
    end
  end

  def remove_node_files(node_name)
    (Leap::Platform.node_files + [:node_files_dir]).each do |path|
      remove_file! [path, node_name]
    end
  end

  #
  # conversions:
  #
  #   "x,y,z" => ["x","y","z"]
  #
  #   "22" => 22
  #
  #   "5.1" => 5.1
  #
  def format_seed_value(v)
    if v =~ /,/
      v = v.split(',')
      v.map! do |i|
        i = i.to_i if i.to_i.to_s == i
        i = i.to_f if i.to_f.to_s == i
        i
      end
    else
      v = v.to_i if v.to_i.to_s == v
      v = v.to_f if v.to_f.to_s == v
    end
    return v
  end

  def validate_ip_address(node)
    if node['ip_address'] == "REQUIRED"
      bail! do
        log :error, "ip_address is not set. Specify with `leap node add NAME ip_address:ADDRESS`."
      end
    end
    IPAddr.new(node['ip_address'])
  rescue ArgumentError
    bail! do
      if node['ip_address']
        log :invalid, "ip_address #{node['ip_address'].inspect}"
      else
        log :missing, "ip_address"
      end
    end
  end

  def assert_valid_node_name!(name, local=false)
    assert! name, 'No <node-name> specified.'
    if local
      assert! name =~ /^[0-9a-z]+$/, "illegal characters used in node name '#{name}' (note: Vagrant does not allow hyphens or underscores)"
    else
      assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}' (note: Linux does not allow underscores)"
    end
  end

end; end