Class: OsCtld::EtcHosts

Inherits:
Object
  • Object
show all
Includes:
OsCtl::Lib::Utils::File
Defined in:
lib/osctld/etc_hosts.rb

Overview

Manipulate localhost hostnames in ‘/etc/hosts` like files

Constant Summary collapse

NOTICE_HEAD =
'### Start of osctld-generated notice'.freeze
NOTICE_BODY =
<<~END.freeze
  # This file is updated by osctld from vpsAdminOS on every VPS start to configure
  # the hostname. If you would like to manage the hostname manually,
  # administrators can configure this by `osctl ct unset hostname` and users
  # in VPS details in vpsAdmin. In addition, osctld will not modify this file
  # if the write by user permission is removed:
  #
  #   chmod u-w /etc/hosts
END
NOTICE_TAIL =
'### End of osctld-generated notice'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ EtcHosts

Returns a new instance of EtcHosts.

Parameters:

  • path (String)

    path to the hosts file



24
25
26
# File 'lib/osctld/etc_hosts.rb', line 24

def initialize(path)
  @path = path
end

Instance Attribute Details

#pathString (readonly)

Returns:

  • (String)


21
22
23
# File 'lib/osctld/etc_hosts.rb', line 21

def path
  @path
end

Instance Method Details

#add_name(line, name, after: nil) ⇒ Object (protected)

Add hostname to ‘line` from `/etc/hosts`

The hostname is put into the first position.

Parameters:

  • line (String)
  • name (String)
  • after (String, nil) (defaults to: nil)


202
203
204
205
206
207
208
209
210
211
# File 'lib/osctld/etc_hosts.rb', line 202

def add_name(line, name, after: nil)
  if after
    replace_name(line, after, "#{after} #{name}")
  else
    return if line !~ /^([^\s]+)(\s+)/

    i = $~.end(2)
    "#{::Regexp.last_match(1)}#{::Regexp.last_match(2)}#{name} #{line[i..]}"
  end
end

#all_names(hostname) ⇒ Object (protected)

Return all names in the order that should be set for a hostname

Parameters:

  • hostname (OsCtl::Lib::Hostname)


176
177
178
179
180
181
182
183
184
185
186
# File 'lib/osctld/etc_hosts.rb', line 176

def all_names(hostname)
  ret = []

  if hostname.local == hostname.fqdn
    ret << hostname.fqdn
  else
    ret << hostname.fqdn << hostname.local
  end

  ret
end

#clear_notice(dst, src) ⇒ Object (protected)



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/osctld/etc_hosts.rb', line 152

def clear_notice(dst, src)
  inside = false

  src.each_line do |line|
    if line.start_with?(NOTICE_HEAD)
      inside = true
      next
    end

    if inside
      if !line.start_with?('#') # malformed notice
        inside = false
      elsif line.start_with?(NOTICE_TAIL)
        inside = false
        next
      end
    end

    dst.write(line) unless inside
  end
end

#do_edit(names) {|new, old| ... } ⇒ Object (protected)

Edit the hosts file and let the caller transform it

If the target file exists, IOs to both new and old files are yielded.

If the target file does not exist, it is created and populated with ‘names`. Nothing is yielded to the caller in that case.

Parameters:

  • names (Array<String>)

    list of hostnames to set

Yield Parameters:

  • new (IO)
  • old (IO)


108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/osctld/etc_hosts.rb', line 108

def do_edit(names)
  regenerate_file(path, 0o644) do |new, old|
    write_notice(new)

    if old
      yield(new, old)
    else
      new.puts("127.0.0.1 #{(names + %w[localhost]).join(' ')}")
      new.puts("::1 #{(names + %w[localhost ip6-localhost ip6-loopback]).join(' ')}")
    end
  end
end

#each_line(io) {|line| ... } ⇒ Object (protected)

Iterate over all lines except the osctld-generated notice

Parameters:

  • io (IO)

Yield Parameters:

  • line (String)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/osctld/etc_hosts.rb', line 124

def each_line(io)
  within_notice = false

  io.each_line do |line|
    if line.start_with?(NOTICE_HEAD)
      within_notice = true
      next
    end

    if within_notice
      if !line.start_with?('#') # malformed notice
        within_notice = false
      elsif line.start_with?(NOTICE_TAIL)
        within_notice = false
        next
      end
    end

    yield(line) unless within_notice
  end
end

#includes_name?(line, name) ⇒ Boolean (protected)

Check if a line of string contains specific hostname

Parameters:

  • line (String)
  • name (String)

Returns:

  • (Boolean)


191
192
193
# File 'lib/osctld/etc_hosts.rb', line 191

def includes_name?(line, name)
  /\s#{Regexp.escape(name)}(\s|$)/ =~ line
end

#replace(old_hostname, new_hostname) ⇒ Object

Parameters:

  • old_hostname (OsCtl::Lib::Hostname)
  • new_hostname (OsCtl::Lib::Hostname)


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
# File 'lib/osctld/etc_hosts.rb', line 56

def replace(old_hostname, new_hostname)
  old_names = all_names(old_hostname)
  new_names = all_names(new_hostname)

  do_edit(new_names) do |new, old|
    each_line(old) do |line|
      if /^127\.0\.0\.1\s/ !~ line && /^::1\s/ !~ line
        new.write(line)
        next
      end

      new_line = line
      last = nil

      zip_all(new_names, old_names).each do |new_name, old_name|
        if old_name && includes_name?(new_line, old_name)
          new_line = replace_name(new_line, old_name, new_name || '')
        elsif new_name
          new_line = add_name(new_line, new_name, after: last)
        end

        last = new_name
      end

      new.write(new_line)
    end
  end
end

#replace_name(line, old_name, new_name) ⇒ Object (protected)

Replace hostname in ‘line` read from `/etc/hosts`

Parameters:

  • line (String)
  • old_name (String)
  • new_name (String)


218
219
220
221
222
223
# File 'lib/osctld/etc_hosts.rb', line 218

def replace_name(line, old_name, new_name)
  line.sub(
    /(\s)#{Regexp.escape(old_name)}(\s|$)/,
    "\\1#{new_name}\\2"
  )
end

#set(hostname) ⇒ Object

Parameters:

  • hostname (OsCtl::Lib::Hostname)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/osctld/etc_hosts.rb', line 29

def set(hostname)
  names = all_names(hostname)

  do_edit(names) do |new, old|
    each_line(old) do |line|
      if /^127\.0\.0\.1\s/ !~ line && /^::1\s/ !~ line
        new.write(line)
        next
      end

      new_line = line
      last = nil

      names.each do |name|
        next if includes_name?(new_line, name)

        new_line = add_name(new_line, name, after: last)
        last = name
      end

      new.write(new_line)
    end
  end
end

#unmanageObject



85
86
87
88
89
90
91
92
93
# File 'lib/osctld/etc_hosts.rb', line 85

def unmanage
  return unless File.exist?(path)

  regenerate_file(path, 0o644) do |new, old|
    next if old.nil?

    clear_notice(new, old)
  end
end

#write_notice(dst) ⇒ Object (protected)



146
147
148
149
150
# File 'lib/osctld/etc_hosts.rb', line 146

def write_notice(dst)
  dst.puts(NOTICE_HEAD)
  dst.write(NOTICE_BODY)
  dst.puts(NOTICE_TAIL)
end

#zip_all(a, b) ⇒ Object (protected)



225
226
227
# File 'lib/osctld/etc_hosts.rb', line 225

def zip_all(a, b)
  [a.size, b.size].max.times.map { |i| [a[i], b[i]] }
end