Files
2024-05-07 18:12:23 +00:00

148 lines
4.9 KiB
Ruby

# frozen_string_literal: true
module Gitlab
module Backup
module Cli
module Metadata
# Backup Metadata includes information about the Backup
class BackupMetadata
# Metadata version number should always increase when:
# - field is added
# - field is removed
# - field format/type changes
METADATA_VERSION = 2
# Metadata filename used when writing or loading from a filesystem location
METADATA_FILENAME = 'backup_information.json'
# Metadata file permissions
METADATA_FILE_MODE = 0o600 # owner: rw, group: none, all: none (-rw-------)
# List all of the current top level fields along with their expected data
# type name to specify how to parse and serialize values
METADATA_SCHEMA = {
metadata_version: :integer,
backup_id: :string,
created_at: :time,
gitlab_version: :string
}.freeze
# Options used by the JSON parser
JSON_PARSE_OPTIONS = {
max_nesting: 1,
allow_nan: false,
symbolize_names: true
}.freeze
# Integer representing the increment of metadata version
# this data was saved with
# @return [Integer]
attr_reader :metadata_version
# Unique ID for a backup
# @return [String]
attr_reader :backup_id
# Timestamp when the backup was created
# @return [Time]
attr_reader :created_at
# Gitlab Version in which the backup was created
# @return [String]
attr_reader :gitlab_version
def initialize(
created_at:,
backup_id:,
gitlab_version:,
metadata_version: METADATA_VERSION
)
@metadata_version = metadata_version
@backup_id = backup_id
@created_at = created_at
@gitlab_version = gitlab_version
end
# Build a new BackupMetadata with defaults
#
# @param [String] gitlab_version
def self.build(gitlab_version:)
created_at = Time.current
backup_id = "#{created_at.strftime('%s_%Y_%m_%d_')}#{gitlab_version}"
new(
backup_id: backup_id,
created_at: created_at,
gitlab_version: gitlab_version
)
end
# Load the metadata from the JSON file stored in the given basepath
#
# @param [String|Pathname] basepath
# @return [Gitlab::Backup::Cli::Metadata::BackupMetadata, nil]
def self.load!(basepath)
basepath = Pathname(basepath) unless basepath.is_a? Pathname
json_file = basepath.join(METADATA_FILENAME)
json = JSON.parse(File.read(json_file), JSON_PARSE_OPTIONS)
deserializer = Gitlab::Backup::Cli::Metadata::Deserializer
parsed_fields = {}
METADATA_SCHEMA.each do |attribute_name, type|
stored_value = json[attribute_name]
parsed_value = deserializer.parse_value(type: type, value: stored_value)
parsed_fields[attribute_name] = parsed_value
end
new(**parsed_fields)
rescue IOError, Errno::ENOENT => e
Gitlab::Backup::Cli::Output.error(
"Failed to load backup information from: #{json_file} (#{e.message})"
)
nil
end
# Expose the information that will be part of the Metadata JSON file
def to_hash
serializer = Gitlab::Backup::Cli::Metadata::Serializer
METADATA_SCHEMA.each_with_object({}) do |(attribute_name, type), output|
# fetch attribute value dynamically
# rubocop:disable GitlabSecurity/PublicSend - we cant use read_attribute here, methods are already limited
value = public_send(attribute_name)
# rubocop:enable GitlabSecurity/PublicSend
serialized_value = serializer.serialize_value(type: type, value: value)
output[attribute_name] = serialized_value
end
end
# Write the metadata to a JSON file in the given basepath
#
# @param [String|Pathname] basepath
# @return [Boolean] whether successfully wrote to the disk
def write!(basepath)
basepath = Pathname(basepath) unless basepath.is_a? Pathname
json_file = basepath.join(METADATA_FILENAME)
json = JSON.pretty_generate(to_hash)
json_file.open(File::RDWR | File::CREAT, METADATA_FILE_MODE) do |file|
file.write(json)
end
true
rescue IOError, Errno::ENOENT => e
Gitlab::Backup::Cli::Output.error("Failed to Backup information to: #{json_file} (#{e.message})")
false
end
end
end
end
end
end