Module: Dash::Utils

Extended by:
Utils
Included in:
Utils
Defined in:
lib/dash/utils.rb

Defined Under Namespace

Classes: Sensitive

Constant Summary collapse

DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX =
/\$(?!{[^\}]*\})/
NUMERIC_DURATION =

kamal-proxy takes durations as Go duration strings; deploy.yml takes plain seconds. Zero is a real value (it disables the timeout), so only nil drops out.

A value that already carries a unit is passed through untouched. Appending "s" to it would not just be redundant, it would change the meaning: "5m" would become "5ms", which Go parses happily as five milliseconds. An operator who writes a Go duration should get the duration they wrote or an error from the proxy, never a silently different one.

/\A-?\d+(\.\d+)?\z/

Instance Method Summary collapse

Instance Method Details

#argumentize(argument, attributes, sensitive: false) ⇒ Object

Return a list of escaped shell arguments using the same named argument against the passed attributes (hash or array).



9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/dash/utils.rb', line 9

def argumentize(argument, attributes, sensitive: false)
  Array(attributes).flat_map do |key, value|
    if value.present?
      attr = "#{key}=#{escape_shell_value(value)}"
      attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
      [ argument, attr ]
    elsif value == false
      [ argument, "#{key}=false" ]
    else
      [ argument, key ]
    end
  end
end

#docker_archObject



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/dash/utils.rb', line 125

def docker_arch
  arch = `docker info --format '{{.Architecture}}'`.strip
  case arch
  when /aarch64/
    "arm64"
  when /x86_64/
    "amd64"
  else
    arch
  end
end

#escape_ascii_shell_value(value) ⇒ Object



96
97
98
99
100
# File 'lib/dash/utils.rb', line 96

def escape_ascii_shell_value(value)
  value.to_s.dump
    .gsub(/`/, '\\\\`')
    .gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
end

#escape_shell_value(value) ⇒ Object

Escape a value to make it safe for shell use.



90
91
92
93
94
# File 'lib/dash/utils.rb', line 90

def escape_shell_value(value)
  value.to_s.scan(/[\x00-\x7F]+|[^\x00-\x7F]+/) \
    .map { |part| part.ascii_only? ? escape_ascii_shell_value(part) : part }
    .join
end

#filter_specific_items(filters, items) ⇒ Object

Apply a list of host or role filters, including wildcard matches



103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/dash/utils.rb', line 103

def filter_specific_items(filters, items)
  matches = []

  Array(filters).select do |filter|
    matches += Array(items).select do |item|
      # Only allow * for a wildcard
      # items are roles or hosts
      File.fnmatch(filter, item.to_s, File::FNM_EXTGLOB)
    end
  end

  matches.uniq
end

#flatten_args(args) ⇒ Object

Flattens a one-to-many structure into an array of two-element arrays each containing a key-value pair



65
66
67
# File 'lib/dash/utils.rb', line 65

def flatten_args(args)
  args.flat_map { |key, value| value.try(:map) { |entry| [ key, entry ] } || [ [ key, value ] ] }
end

#join_commands(commands) ⇒ Object



121
122
123
# File 'lib/dash/utils.rb', line 121

def join_commands(commands)
  commands.map(&:strip).join(" ")
end

#older_version?(version, other_version) ⇒ Boolean

Returns:

  • (Boolean)


137
138
139
# File 'lib/dash/utils.rb', line 137

def older_version?(version, other_version)
  Gem::Version.new(version.delete_prefix("v")) < Gem::Version.new(other_version.delete_prefix("v"))
end

#optionize(args, with: nil, escape: true) ⇒ Object

Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option. A Sensitive value stays Sensitive: the rendered option keeps the real value for execution and a redacted form for anything kamal prints - same contract as argumentize's sensitive: kwarg, but decided per value by the caller.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dash/utils.rb', line 27

def optionize(args, with: nil, escape: true)
  options = if with
    flatten_args(args).collect do |(key, value)|
      if value == true
        "--#{key}"
      else
        rendered = "--#{key}#{with}#{escape ? escape_shell_value(value) : value}"
        value.is_a?(Dash::Utils::Sensitive) ? sensitive(rendered, redaction: "--#{key}#{with}#{value.redaction}") : rendered
      end
    end
  else
    flatten_args(args).collect do |(key, value)|
      rendered = value == true ? nil : escape ? escape_shell_value(value) : value
      rendered = sensitive(rendered, redaction: value.redaction) if value.is_a?(Dash::Utils::Sensitive)
      [ "--#{key}", rendered ]
    end
  end

  options.flatten.compact
end

#redacted(value) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/dash/utils.rb', line 76

def redacted(value)
  case
  when value.respond_to?(:redaction)
    value.redaction
  when value.respond_to?(:transform_values)
    value.transform_values { |value| redacted value }
  when value.respond_to?(:map)
    value.map { |element| redacted element }
  else
    value
  end
end

#seconds_duration(value) ⇒ Object



58
59
60
61
62
# File 'lib/dash/utils.rb', line 58

def seconds_duration(value)
  return if value.nil?

  value.to_s.match?(NUMERIC_DURATION) ? "#{value}s" : value.to_s
end

#sensitiveObject

Marks sensitive values for redaction in logs and human-visible output. Pass redaction: to change the default "[REDACTED]" redaction, e.g. `sensitive "#arg=#secret", redaction: "#arg=xxxx"



72
73
74
# File 'lib/dash/utils.rb', line 72

def sensitive(...)
  Dash::Utils::Sensitive.new(...)
end

#stable_sort!(elements, &block) ⇒ Object



117
118
119
# File 'lib/dash/utils.rb', line 117

def stable_sort!(elements, &block)
  elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
end