Class: Migration

Inherits:
Object
  • Object
show all
Defined in:
lib/migration.rb

Overview

SystemDescription Migrations

Migrations are used for migrating descriptions with an older format version to the current version. They are defined as subclasses of ‘Migration` in `schema/migrations`.

Naming schema

Migrations need to follow a naming schema defining which format version they are working on. ‘Migrate1To2` defines a migration which converts a version 1 description to a version 2 one, for example.

Defining migrations

The migration classes need to define a ‘migrate` method which does the actual migration. The raw hash of the system description in question is made available as the `@hash` instance variable, the path to the description on disk is given ash the `@path` instance variable.

Migrations also need to describe their purpose using the ‘desc` class method (see example below).

Note: Migrations do not need to take care of updating the format version attribute in the system description. That is already handled by the base class.

Simple example migration which adds a new attribute to the JSON:

class Migrate1To2 < Migration
  desc <<-EOT
    Add 'foo' element to the system description root.
  EOT

  def migrate
    is_extracted = Dir.exists?(File.join(@path, "config-files"))
    @hash["config_files"]["extracted"] = is_extracted
  end
end

Constant Summary collapse

MIGRATIONS_DIR =
File.join(Machinery::ROOT, "schema/migrations")

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash, path) ⇒ Migration

Returns a new instance of Migration.



155
156
157
158
# File 'lib/migration.rb', line 155

def initialize(hash, path)
  @hash = hash
  @path = path
end

Class Attribute Details

.migration_descObject (readonly)

Returns the value of attribute migration_desc.



60
61
62
# File 'lib/migration.rb', line 60

def migration_desc
  @migration_desc
end

Instance Attribute Details

#hashObject

Returns the value of attribute hash.



150
151
152
# File 'lib/migration.rb', line 150

def hash
  @hash
end

#pathObject

Returns the value of attribute path.



151
152
153
# File 'lib/migration.rb', line 151

def path
  @path
end

Class Method Details

.desc(s) ⇒ Object



62
63
64
# File 'lib/migration.rb', line 62

def desc(s)
  @migration_desc = s
end

.migrate_description(store, description_name, options = {}) ⇒ Object



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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/migration.rb', line 66

def migrate_description(store, description_name, options = {})
  load_migrations

  hash = Manifest.load(description_name, store.manifest_path(description_name)).to_hash

  errors = JsonValidator.new(hash).validate
  errors += FileValidator.new(hash, store.description_path(description_name)).validate
  if !errors.empty?
    if options[:force]
      Machinery::Ui.warn("Warning: System Description validation errors:")
      Machinery::Ui.warn(errors)
    else
      raise Machinery::Errors::SystemDescriptionValidationFailed.new(errors)
    end
  end

  current_version = hash["meta"]["format_version"]
  if !current_version
    raise Machinery::Errors::SystemDescriptionIncompatible.new(
      "The system description '#{description_name}' was generated by an old " \
      "version of machinery that is not supported by the upgrade mechanism."
    )
  end

  if current_version == SystemDescription::CURRENT_FORMAT_VERSION
    Machinery::Ui.puts "System description \"#{description_name}\" is up to " \
      "date, no upgrade necessary."
    return false
  end

  backup_description = store.backup(description_name)
  backup_path = store.description_path(backup_description)
  backup_hash = Manifest.load(
    backup_description, store.manifest_path(backup_description)
  ).to_hash

  (current_version..SystemDescription::CURRENT_FORMAT_VERSION-1).each do |version|
    next_version = version + 1
    begin
      klass = Object.const_get("Migrate#{version}To#{next_version}")
    rescue NameError
      return
    end

    # Make sure that the migration was documented
    if klass.migration_desc.nil?
      raise Machinery::Errors::MigrationError.new(
        "Invalid migration '#{klass}'. It does not specify its purpose using" \
        " the 'desc' class method."
      )
    end

    klass.new(backup_hash, backup_path).migrate
    backup_hash["meta"]["format_version"] = next_version
  end

  File.write(store.manifest_path(backup_description), JSON.pretty_generate(backup_hash))

  if options[:force]
    store.swap(description_name, backup_description)
    Machinery::Ui.puts "Saved backup to #{backup_path}"
  else
    begin
      SystemDescription.load!(backup_description, store)
      store.remove(description_name)
      store.rename(backup_description, description_name)
    rescue Machinery::Errors::SystemDescriptionError
      store.remove(backup_description)
      raise
    end
  end

  true
end