Class: Restic::Service::Conf

Inherits:
Object
  • Object
show all
Defined in:
lib/restic/service/conf.rb

Overview

The overall service configuration

This is the API side of the service configuration. The configuration is usually stored on disk in YAML and

The YAML format is as follows:

# The path to the underlying tools
tools:
  restic: /opt/restic
  rclone: /opt/rclone

# The targets. The only generic parts of a target definition
# are the name and type. The rest is target-specific
targets:
  - name: a_restic_sftp_target
    type: restic-sftp

See the README.md for more details about available targets

Defined Under Namespace

Classes: InvalidConfigurationFile, NoSuchTarget

Constant Summary collapse

TARGET_CLASS_FROM_TYPE =
TOOLS =
%w{restic rclone}
BANDWIDTH_SCALES =

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(conf_path) ⇒ Conf

Returns a new instance of Conf.



130
131
132
133
134
135
136
137
138
# File 'lib/restic/service/conf.rb', line 130

def initialize(conf_path)
    @conf_path = conf_path
    @targets = Hash.new
    @period  = 3600
    @tools   = Hash.new
    TOOLS.each do |tool_name|
        @tools[tool_name] = find_in_path(tool_name)
    end
end

Instance Attribute Details

#bandwidth_limitnil, Integer (readonly)

The bandwidth limit in bytes/s

Default is nil (none)

Returns:

  • (nil, Integer)


128
129
130
# File 'lib/restic/service/conf.rb', line 128

def bandwidth_limit
  @bandwidth_limit
end

#conf_pathPathname (readonly)

The configuration path

Returns:

  • (Pathname)


114
115
116
# File 'lib/restic/service/conf.rb', line 114

def conf_path
  @conf_path
end

#periodInteger (readonly)

The polling period in seconds

Default is 1h (3600s)

Returns:

  • (Integer)


121
122
123
# File 'lib/restic/service/conf.rb', line 121

def period
  @period
end

Class Method Details

.default_confObject

The default (empty) configuration



29
30
31
32
33
34
# File 'lib/restic/service/conf.rb', line 29

def self.default_conf
    Hash['targets' => [],
         'period' => 3600,
         'bandwidth_limit' => nil,
         'tools' => Hash.new]
end

.load(path) ⇒ Conf

Load a configuration file

Parameters:

  • (Pathname)

Returns:

Raises:



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/restic/service/conf.rb', line 98

def self.load(path)
    if !path.file?
        return Conf.new(Pathname.new(""))
    end

    yaml = YAML.load(path.read) || Hash.new
    yaml = normalize_yaml(yaml)

    conf = Conf.new(path.dirname)
    conf.load_from_yaml(yaml)
    conf
end

.normalize_yaml(yaml) ⇒ Object

Normalizes and validates a configuration hash, as stored in YAML



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
# File 'lib/restic/service/conf.rb', line 58

def self.normalize_yaml(yaml)
    yaml = default_conf.merge(yaml)
    TOOLS.each do |tool_name|
        yaml['tools'][tool_name] ||= tool_name
    end

    yaml['auto_update'] ||= Array.new

    target_names = Array.new
    yaml['targets'] = yaml['targets'].map do |target|
        if !target['name']
            raise InvalidConfigurationFile, "missing 'name' field in target"
        elsif !target['type']
            raise InvalidConfigurationFile, "missing 'type' field in target"
        end

        target_class = target_class_from_type(target['type'])
        if !target_class
            raise InvalidConfigurationFile, "target type #{target['type']} does not exist, available targets: #{TARGET_CLASS_FROM_TYPE.keys.sort.join(", ")}"
        end

        name = target['name'].to_s
        if target_names.include?(name)
            raise InvalidConfigurationFile, "duplicate target name '#{name}'"
        end

        target = target.dup
        target['name'] = name
        target = target_class.normalize_yaml(target)
        target_names << name
        target
    end
    yaml
end

.parse_bandwidth_limit(limit) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/restic/service/conf.rb', line 146

def self.parse_bandwidth_limit(limit)
    if !limit.respond_to?(:to_str)
        return Integer(limit)
    else
        match = /^(\d+)\s*(k|m|g)?$/.match(limit.downcase)
        if match
            return Integer(match[1]) * BANDWIDTH_SCALES.fetch(match[2])
        else
            raise ArgumentError, "cannot interpret '#{limit}' as a valid bandwidth limit, give a plain number in bytes or use the k, M and G suffixes"
        end
    end
end

.target_class_from_type(type) ⇒ Object

Returns the target class that will handle the given target type

Parameters:

  • type (String)

    the type as represented in the YAML file



47
48
49
50
51
52
53
# File 'lib/restic/service/conf.rb', line 47

def self.target_class_from_type(type)
    if target_class = TARGET_CLASS_FROM_TYPE[type]
        return target_class
    else
        raise InvalidConfigurationFile, "target type #{type} does not exist, available targets: #{TARGET_CLASS_FROM_TYPE.keys.sort.join(", ")}"
    end
end

Instance Method Details

#auto_update_restic?Boolean

Returns:

  • (Boolean)


224
225
226
# File 'lib/restic/service/conf.rb', line 224

def auto_update_restic?
    @auto_update_restic
end

#auto_update_restic_service?Boolean

Returns:

  • (Boolean)


220
221
222
# File 'lib/restic/service/conf.rb', line 220

def auto_update_restic_service?
    @auto_update_restic_service
end

#conf_keys_path_for(target) ⇒ Object

The path to the key file for the given target



160
161
162
# File 'lib/restic/service/conf.rb', line 160

def conf_keys_path_for(target)
    conf_path.join("keys", "#{target.name}.keys")
end

#each_target(&block) ⇒ Object

Enumerates the targets



178
179
180
# File 'lib/restic/service/conf.rb', line 178

def each_target(&block)
    @targets.each_value(&block)
end

#find_in_path(name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Helper that resolves a binary in PATH



190
191
192
193
194
195
196
197
198
# File 'lib/restic/service/conf.rb', line 190

def find_in_path(name)
    ENV['PATH'].split(File::PATH_SEPARATOR).each do |p|
        candidate = Pathname.new(p).join(name)
        if candidate.file?
            return candidate
        end
    end
    nil
end

#load_from_yaml(yaml) ⇒ void

This method returns an undefined value.

Add the information stored in a YAML-like hash into this configuration

Parameters:

  • the (Hash)

    configuration, following the documented configuration format (see Restic::Service::Conf)



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/restic/service/conf.rb', line 238

def load_from_yaml(yaml)
    load_tools_from_yaml(yaml['tools'])
    @period = Integer(yaml['period'])
    @bandwidth_limit = if limit_yaml = yaml['bandwidth_limit']
                           Conf.parse_bandwidth_limit(limit_yaml)
                       end

    yaml['auto_update'].each do |update_target, do_update|
        if update_target == 'restic-service'
            @auto_update_restic_service = do_update
        elsif update_target == 'restic'
            @auto_update_restic = do_update
        end
    end

    yaml['targets'].each do |yaml_target|
        type = yaml_target['type']
        target_class = Conf.target_class_from_type(type)
        target = target_class.new(yaml_target['name'])
        target.setup_from_conf(self, yaml_target)
        register_target(target)
    end
end

#load_tools_from_yaml(yaml) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Helper for #load_from_yaml



265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/restic/service/conf.rb', line 265

def load_tools_from_yaml(yaml)
    TOOLS.each do |tool_name|
        tool_path = Pathname.new(yaml[tool_name])
        if tool_path.relative?
            tool_path = find_in_path(tool_path)
        end
        if tool_path && tool_path.file?
            @tools[tool_name] = tool_path
        else
            STDERR.puts "cannot find path to #{tool_name}"
            @tools.delete(tool_name)
        end
    end
end

#register_target(target) ⇒ Object

Registers a target



183
184
185
# File 'lib/restic/service/conf.rb', line 183

def register_target(target)
    @targets[target.name] = target
end

#restic_platformObject



228
229
230
# File 'lib/restic/service/conf.rb', line 228

def restic_platform
    @auto_update_restic
end

#target_by_name(name) ⇒ Target

Gets a target configuration

Parameters:

  • name (String)

    the target name

Returns:

  • (Target)

Raises:

  • NoSuchTarget



169
170
171
172
173
174
175
# File 'lib/restic/service/conf.rb', line 169

def target_by_name(name)
    if target = @targets[name]
        target
    else
        raise NoSuchTarget, "no target named '#{name}'"
    end
end

#tool_available?(tool_name) ⇒ Boolean

Checks whether a given tool is available

Parameters:

  • (String)

Returns:

  • (Boolean)


204
205
206
# File 'lib/restic/service/conf.rb', line 204

def tool_available?(tool_name)
    @tools.has_key?(tool_name)
end

#tool_path(tool_name) ⇒ Pathname

The full path of a given tool

Parameters:

  • (String)

Returns:

  • (Pathname)


212
213
214
215
216
217
218
# File 'lib/restic/service/conf.rb', line 212

def tool_path(tool_name)
    if tool = @tools[tool_name]
        tool
    else
        raise ArgumentError, "cound not find '#{tool_name}'"
    end
end