Module: Mxrb::OfficialMarketplace

Defined in:
lib/mxrb/official_marketplace.rb,
lib/mxrb/official_marketplace/lifecycle.rb,
lib/mxrb/official_marketplace/content_api.rb,
lib/mxrb/official_marketplace/dependency_resolver.rb,
lib/mxrb/official_marketplace/module_package_importer.rb,
lib/mxrb/official_marketplace/widget_package_installer.rb

Overview

Installer for official/community Mendix packages, separate from Ruby modules.

Defined Under Namespace

Classes: AuditResult, ContentApi, Credentials, Dependency, DependencyPlan, DependencyResolver, GitHubResolver, HttpClient, Installation, Installer, Lifecycle, LifecyclePlan, ModuleInstallation, ModulePackageAssets, ModulePackageImporter, ModulePackageInventory, ModulePackageReader, OfficialPackage, Package, PackageEnvelope, SecurityAuditor, WidgetBundleInventory, WidgetDependency, WidgetInstallation, WidgetPackageInstaller, WidgetPackageInventory

Constant Summary collapse

CATALOG =
{
  'CommunityCommons' => 'mendix/CommunityCommons',
  'UnitTesting' => 'mendix/UnitTesting'
}.freeze

Class Method Summary collapse

Class Method Details

.lock(target) ⇒ Object



44
45
46
47
48
49
# File 'lib/mxrb/official_marketplace.rb', line 44

def self.lock(target)
  path = File.join(File.expand_path(target), '.mxrb', 'marketplace.lock.json')
  File.file?(path) ? JSON.parse(File.read(path)) : { 'packages' => {} }
rescue JSON::ParserError => e
  raise MarketplaceError, "invalid marketplace lockfile: #{e.message}"
end

.module_by_name(mpr, name) ⇒ Object



110
111
112
# File 'lib/mxrb/official_marketplace.rb', line 110

def self.module_by_name(mpr, name)
  mpr.units_by_containment('Modules').find { mpr.parse_contents(_1)['Name'] == name }
end

.module_present?(mpr_path, name, module_id) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
104
105
106
107
108
# File 'lib/mxrb/official_marketplace.rb', line 98

def self.module_present?(mpr_path, name, module_id)
  return false unless File.file?(mpr_path)

  mpr = IO::MprFile.open(mpr_path, readonly: true)
  unit = module_id ? mpr.unit(module_id) : module_by_name(mpr, name)
  unit && mpr.parse_contents(unit)['Name'] == name
rescue Error, SQLite3::Exception
  false
ensure
  mpr&.close
end

.tree_digest(path) ⇒ Object



34
35
36
37
38
39
40
41
42
# File 'lib/mxrb/official_marketplace.rb', line 34

def self.tree_digest(path)
  digest = Digest::SHA256.new
  Dir.glob(File.join(path, '**', '*'), File::FNM_DOTMATCH).sort.each do |file|
    next unless File.file?(file)

    digest << file.delete_prefix("#{path}/") << "\0" << File.binread(file)
  end
  digest.hexdigest
end

.verify(target) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/mxrb/official_marketplace.rb', line 51

def self.verify(target)
  root = File.expand_path(target)
  lock(root).fetch('packages').to_h do |name, entry|
    result = case entry['kind']
             when 'module' then verify_module(root, name, entry)
             when 'widget' then verify_widget(root, entry)
             else verify_tree(root, entry)
             end
    [name, result]
  end
end

.verify_module(root, name, entry) ⇒ Object

rubocop:disable Metrics/AbcSize



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/mxrb/official_marketplace.rb', line 86

def self.verify_module(root, name, entry) # rubocop:disable Metrics/AbcSize
  archive = File.join(root, entry.fetch('archive'))
  actual = File.file?(archive) ? Digest::SHA256.file(archive).hexdigest : nil
  mpr_path = File.join(root, entry.fetch('destination'))
  present = module_present?(mpr_path, name, entry['module_id'])
  files_present = Array(entry['files']).all? { File.file?(File.join(root, _1)) }
  {
    valid: actual == entry['sha256'] && present && files_present,
    expected: entry['sha256'], actual:, module_present: present, files_present:
  }
end

.verify_tree(root, entry) ⇒ Object

rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity



80
81
82
83
84
# File 'lib/mxrb/official_marketplace.rb', line 80

def self.verify_tree(root, entry)
  destination = File.join(root, entry.fetch('destination'))
  actual = File.directory?(destination) ? tree_digest(destination) : nil
  { valid: actual == entry['sha256'], expected: entry['sha256'], actual: }
end

.verify_widget(root, entry) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity



64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/mxrb/official_marketplace.rb', line 64

def self.verify_widget(root, entry)
  destination = File.join(root, entry.fetch('destination'))
  archive = File.join(root, entry.fetch('archive'))
  actual = File.file?(destination) ? Digest::SHA256.file(destination).hexdigest : nil
  cached = File.file?(archive) ? Digest::SHA256.file(archive).hexdigest : nil
  inventory = WidgetPackageInventory.read(destination) if actual == entry['sha256']
  identity = inventory&.name == entry['widget_name'] && inventory&.widget_ids == entry['widget_ids']
  {
    valid: actual == entry['sha256'] && cached == entry['sha256'] && identity,
    expected: entry['sha256'], actual:, cached:, identity:
  }
rescue MarketplaceError
  { valid: false, expected: entry['sha256'], actual:, cached:, identity: false }
end