summaryrefslogtreecommitdiff
path: root/lib/chef/knife/config_list_profiles.rb
blob: 5b971345c61b06c7daeea5be5e1fa4ee31a673f0 (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
#
# Copyright:: Copyright (c) 2018, Noah Kantrowitz
# 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 "chef/knife"
require "chef/workstation_config_loader"

class Chef
  class Knife
    class ConfigListProfiles < Knife
      banner "knife config list-profiles (options)"

      option :ignore_knife_rb,
             short: "-i",
             long: "--ignore-knife-rb",
             description: "Ignore the current config.rb/knife.rb configuration.",
             default: false

      def run
        credentials_data = self.class.config_loader.parse_credentials_file
        if credentials_data.nil? || credentials_data.empty?
          # Should this just show the ambient knife.rb config as "default" instead?
          ui.fatal("No profiles found, #{self.class.config_loader.credentials_file_path} does not exist or is empty")
          exit 1
        end

        current_profile = self.class.config_loader.credentials_profile(config[:profile])
        profiles = credentials_data.keys.map do |profile|
          if config[:ignore_knife_rb]
            # Don't do any fancy loading nonsense, just the raw data.
            profile_data = credentials_data[profile]
            {
              profile: profile,
              active: profile == current_profile,
              client_name: profile_data["client_name"] || profile_data["node_name"],
              client_key: profile_data["client_key"],
              server_url: profile_data["chef_server_url"],
            }
          else
            # Fancy loading nonsense so we get what the actual config would be.
            # Note that this modifies the global config, after this, all bets are
            # off as to whats in the config.
            Chef::Config.reset
            wcl = Chef::WorkstationConfigLoader.new(nil, Chef::Log, profile: profile)
            wcl.load
            {
              profile: profile,
              active: profile == current_profile,
              client_name: Chef::Config[:node_name],
              client_key: Chef::Config[:client_key],
              server_url: Chef::Config[:chef_server_url],
            }
          end
        end

        # Try to reset the config.
        unless config[:ignore_knife_rb]
          Chef::Config.reset
          Chef::WorkstationConfigLoader.new(config[:config_file], Chef::Log, profile: config[:profile]).load
          apply_computed_config
        end

        if ui.interchange?
          # Machine-readable output.
          ui.output(profiles)
        else
          # Table output.
          ui.output(render_table(profiles))
        end
      end

      private

      def render_table(profiles, padding: 2)
        # Replace the home dir in the client key path with ~.
        profiles.each do |profile|
          profile[:client_key] = profile[:client_key].to_s.gsub(/^#{Regexp.escape(Dir.home)}/, "~") if profile[:client_key]
        end
        # Render the data to a 2D array that will be used for the table.
        table_data = [["", "Profile", "Client", "Key", "Server"]] + profiles.map do |profile|
          [profile[:active] ? "*" : ""] + profile.values_at(:profile, :client_name, :client_key, :server_url).map(&:to_s)
        end
        # Compute column widths.
        column_widths = Array.new(table_data.first.length) do |i|
          table_data.map { |row| row[i].length + padding }.max
        end
        # Special case, the first col gets no padding (because indicator) and last
        # get no padding because last.
        column_widths[0] -= padding
        column_widths[-1] -= padding
        # Build the format string for each row.
        format_string = column_widths.map { |w| "%-#{w}.#{w}s" }.join("")
        format_string << "\n"
        # Print the header row and a separator.
        table = ui.color(format_string % table_data.first, :green)
        table << "-" * column_widths.sum
        table << "\n"
        # Print the rest of the table.
        table_data.drop(1).each do |row|
          table << format_string % row
        end
        # Trim the last newline because ui.output adds one.
        table.chomp!
      end

    end
  end
end