From e4ea997399732427fb59dcbb4194fdba55bfe9c0 Mon Sep 17 00:00:00 2001
From: Mathieu Parent <math.parent@gmail.com>
Date: Sun, 28 Mar 2021 22:53:28 +0200
Subject: [PATCH] Add Packages::Helm::ExtractFileMetadataService

Item 2.3 of https://gitlab.com/gitlab-org/gitlab/-/issues/18997#note_530270349
---
 .../helm/extract_file_metadata_service.rb     | 53 +++++++++++++++++
 .../extract_file_metadata_service_spec.rb     | 59 +++++++++++++++++++
 2 files changed, 112 insertions(+)
 create mode 100644 app/services/packages/helm/extract_file_metadata_service.rb
 create mode 100644 spec/services/packages/helm/extract_file_metadata_service_spec.rb

diff --git a/app/services/packages/helm/extract_file_metadata_service.rb b/app/services/packages/helm/extract_file_metadata_service.rb
new file mode 100644
index 00000000000..e7373d8ea8f
--- /dev/null
+++ b/app/services/packages/helm/extract_file_metadata_service.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'rubygems/package'
+
+module Packages
+  module Helm
+    class ExtractFileMetadataService
+      ExtractionError = Class.new(StandardError)
+
+      def initialize(package_file)
+        @package_file = package_file
+      end
+
+      def execute
+        raise ExtractionError, 'invalid package file' unless valid_package_file?
+
+        metadata
+      end
+
+      private
+
+      def valid_package_file?
+        @package_file &&
+          @package_file.package&.helm? &&
+          @package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
+      end
+
+      def metadata
+        YAML.safe_load(chart_yaml_content)
+      rescue Psych::Exception => e
+        raise ExtractionError, "Error while parsing Chart.yaml: #{e.message}"
+      end
+
+      def chart_yaml_content
+        @package_file.file.use_open_file do |file|
+          tar_reader = Gem::Package::TarReader.new(Zlib::GzipReader.new(file))
+
+          chart_yaml = tar_reader.find do |entry|
+            next unless entry.file?
+
+            entry.full_name.end_with?('/Chart.yaml')
+          end
+
+          raise ExtractionError, 'Chart.yaml not found within a directory' unless chart_yaml
+
+          chart_yaml.read
+        ensure
+          tar_reader.close
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/packages/helm/extract_file_metadata_service_spec.rb b/spec/services/packages/helm/extract_file_metadata_service_spec.rb
new file mode 100644
index 00000000000..ea196190e24
--- /dev/null
+++ b/spec/services/packages/helm/extract_file_metadata_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Helm::ExtractFileMetadataService do
+  let_it_be(:package_file) { create(:helm_package_file) }
+
+  let(:service) { described_class.new(package_file) }
+
+  let(:expected) do
+    {
+      'apiVersion' => 'v2',
+      'description' => 'File, Block, and Object Storage Services for your Cloud-Native Environment',
+      'icon' => 'https://rook.io/images/rook-logo.svg',
+      'name' => 'rook-ceph',
+      'sources' => ['https://github.com/rook/rook'],
+      'version' => 'v1.5.8'
+    }
+  end
+
+  subject { service.execute }
+
+  context 'with a valid file' do
+    it { is_expected.to eq(expected) }
+  end
+
+  context 'without Chart.yaml' do
+    before do
+      expect_next_instances_of(Gem::Package::TarReader::Entry, 14) do |entry|
+        expect(entry).to receive(:full_name).exactly(:once).and_wrap_original do |m, *args|
+          m.call(*args) + '_suffix'
+        end
+      end
+    end
+
+    it { expect { subject }.to raise_error(described_class::ExtractionError, 'Chart.yaml not found within a directory') }
+  end
+
+  context 'with Chart.yaml at root' do
+    before do
+      expect_next_instances_of(Gem::Package::TarReader::Entry, 14) do |entry|
+        expect(entry).to receive(:full_name).exactly(:once) do
+          'Chart.yaml'
+        end
+      end
+    end
+
+    it { expect { subject }.to raise_error(described_class::ExtractionError, 'Chart.yaml not found within a directory') }
+  end
+
+  context 'with an invalid YAML' do
+    before do
+      expect_next_instance_of(Gem::Package::TarReader::Entry) do |entry|
+        expect(entry).to receive(:read).and_return('{')
+      end
+    end
+
+    it { expect { subject }.to raise_error(described_class::ExtractionError, 'Error while parsing Chart.yaml: (<unknown>): did not find expected node content while parsing a flow node at line 2 column 1') }
+  end
+end
-- 
2.30.9