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.



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

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)


130
131
132
# File 'lib/restic/service/conf.rb', line 130

def bandwidth_limit
  @bandwidth_limit
end

#conf_pathPathname (readonly)

The configuration path

Returns:

  • (Pathname)


116
117
118
# File 'lib/restic/service/conf.rb', line 116

def conf_path
  @conf_path
end

#periodInteger (readonly)

The polling period in seconds

Default is 1h (3600s)

Returns:

  • (Integer)


123
124
125
# File 'lib/restic/service/conf.rb', line 123

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:



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

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



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

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



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

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



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

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_rclone?Boolean

Returns:

  • (Boolean)


235
236
237
# File 'lib/restic/service/conf.rb', line 235

def auto_update_rclone?
    @auto_update_rclone
end

#auto_update_restic?Boolean

Returns:

  • (Boolean)


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

def auto_update_restic?
    @auto_update_restic
end

#auto_update_restic_service?Boolean

Returns:

  • (Boolean)


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

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



162
163
164
# File 'lib/restic/service/conf.rb', line 162

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

#each_target(&block) ⇒ Object

Enumerates the targets



180
181
182
# File 'lib/restic/service/conf.rb', line 180

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



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

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)



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/restic/service/conf.rb', line 249

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
        elsif update_target == 'rclone'
            @auto_update_rclone = 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



278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/restic/service/conf.rb', line 278

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

        exists = tool_path.file?
        STDERR.puts "#{tool_path} does not exist" unless exists
        @tools[tool_name] = [tool_path, exists]
    end
end

#rclone_platformObject



239
240
241
# File 'lib/restic/service/conf.rb', line 239

def rclone_platform
    @auto_update_rclone
end

#register_target(target) ⇒ Object

Registers a target



185
186
187
# File 'lib/restic/service/conf.rb', line 185

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

#restic_platformObject



231
232
233
# File 'lib/restic/service/conf.rb', line 231

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



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

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)


206
207
208
209
# File 'lib/restic/service/conf.rb', line 206

def tool_available?(tool_name)
    _, available = @tools[tool_name]
    available
end

#tool_path(tool_name, only_if_present: true) ⇒ Pathname

The full path of a given tool

Parameters:

  • (String)

Returns:

  • (Pathname)


215
216
217
218
219
220
221
# File 'lib/restic/service/conf.rb', line 215

def tool_path(tool_name, only_if_present: true)
    if tool = @tools[tool_name]
        tool[0] if tool[1] || !only_if_present
    else
        raise ArgumentError, "cound not find '#{tool_name}'"
    end
end