puppet-forge-server 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.travis.yml +3 -0
  4. data/lib/puppet_forge_server.rb +4 -0
  5. data/lib/puppet_forge_server/api/v1/modules.rb +7 -3
  6. data/lib/puppet_forge_server/api/v3/modules.rb +7 -2
  7. data/lib/puppet_forge_server/api/v3/releases.rb +9 -4
  8. data/lib/puppet_forge_server/app/frontend.rb +19 -0
  9. data/lib/puppet_forge_server/app/public/css/puppetlabs.css +236 -0
  10. data/lib/puppet_forge_server/app/views/layout.haml +1 -1
  11. data/lib/puppet_forge_server/app/views/module.haml +71 -0
  12. data/lib/puppet_forge_server/app/views/modules.haml +1 -1
  13. data/lib/puppet_forge_server/backends/directory.rb +12 -5
  14. data/lib/puppet_forge_server/backends/proxy.rb +29 -4
  15. data/lib/puppet_forge_server/backends/proxy_v3.rb +20 -6
  16. data/lib/puppet_forge_server/http/http_client.rb +29 -2
  17. data/lib/puppet_forge_server/logger.rb +9 -2
  18. data/lib/puppet_forge_server/models/builder.rb +6 -1
  19. data/lib/puppet_forge_server/models/metadata.rb +1 -1
  20. data/lib/puppet_forge_server/models/module.rb +2 -2
  21. data/lib/puppet_forge_server/server.rb +2 -0
  22. data/lib/puppet_forge_server/utils/archiver.rb +3 -1
  23. data/lib/puppet_forge_server/utils/cache_provider.rb +42 -0
  24. data/lib/puppet_forge_server/utils/encoding.rb +46 -0
  25. data/lib/puppet_forge_server/utils/filtering_inspecter.rb +30 -0
  26. data/lib/puppet_forge_server/utils/md_renderer.rb +54 -0
  27. data/lib/puppet_forge_server/utils/option_parser.rb +23 -1
  28. data/lib/puppet_forge_server/version.rb +1 -1
  29. data/puppet-forge-server.gemspec +8 -0
  30. data/spec/spec_helper.rb +41 -1
  31. data/spec/unit/http/http_client_spec.rb +93 -0
  32. data/spec/unit/utils/encoding_spec.rb +35 -0
  33. metadata +110 -2
@@ -20,7 +20,7 @@
20
20
  - modules.each do |element|
21
21
  %li{:class => element['private'] ? 'clearfix private' : 'clearfix'}
22
22
  .col
23
- %h3= "#{element['owner']['username']}/#{element['name']}"
23
+ %a{:class => "h3", :href => "/module?name=#{element['current_release']['metadata']['name']}" }= "#{element['owner']['username']}/#{element['name']}"
24
24
  %p= element['current_release']['metadata']['summary']
25
25
  %span.release-info= "Version #{element['current_release']['metadata']['version']}"
26
26
  - if element['current_release']['metadata']['issues_url']
@@ -56,9 +56,15 @@ module PuppetForgeServer::Backends
56
56
  end
57
57
 
58
58
  private
59
- def read_metadata(archive_path)
60
- metadata_file = read_from_archive(archive_path, %r[[^/]+/metadata\.json$])
61
- JSON.parse(metadata_file)
59
+ def read_module_data(archive_path)
60
+ file_contents = read_from_archive(archive_path, %r[^([^/]+/)?(metadata\.json|README\.md)$])
61
+ metadata = JSON.parse(file_contents.find {|key, value| key =~ /metadata\.json/}[1])
62
+ begin
63
+ readme = file_contents.find {|key, value| key =~ /README\.md/}[1]
64
+ rescue
65
+ readme = nil
66
+ end
67
+ [metadata, readme]
62
68
  rescue => error
63
69
  warn "Error reading from module archive #{archive_path}: #{error}"
64
70
  return nil
@@ -80,14 +86,15 @@ module PuppetForgeServer::Backends
80
86
  options = ({:with_checksum => true}).merge(options)
81
87
  modules = []
82
88
  Dir["#{@module_dir}/**/#{file_name}"].each do |path|
83
- metadata_raw = read_metadata(path)
89
+ metadata_raw, readme = read_module_data(path)
84
90
  if metadata_raw
85
91
  modules <<
86
92
  PuppetForgeServer::Models::Module.new({
87
93
  :metadata => parse_dependencies(PuppetForgeServer::Models::Metadata.new(normalize_metadata(metadata_raw))),
88
94
  :checksum => options[:with_checksum] == true ? Digest::MD5.file(path).hexdigest : nil,
89
95
  :path => "/#{File.basename(path)}",
90
- :private => ! @readonly
96
+ :private => ! @readonly,
97
+ :readme => readme
91
98
  })
92
99
  else
93
100
  @log.error "Failed reading metadata from #{path}"
@@ -35,14 +35,18 @@ module PuppetForgeServer::Backends
35
35
 
36
36
  def get_file_buffer(relative_path)
37
37
  file_name = relative_path.split('/').last
38
- File.join(@cache_dir, file_name[0].downcase, file_name)
38
+ target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
39
39
  path = Dir["#{@cache_dir}/**/#{file_name}"].first
40
40
  unless File.exist?("#{path}")
41
41
  buffer = download("#{@file_path.chomp('/')}/#{relative_path}")
42
- File.open(File.join(@cache_dir, file_name[0].downcase, file_name), 'wb') do |file|
43
- file.write(buffer.read)
42
+ File.open(target_file, 'wb') do |file|
43
+ bytes = buffer.read
44
+ file.write(bytes)
45
+ @log.debug("Saved #{bytes.size} bytes in filesystem cache for path: #{relative_path}, target file: #{target_file}")
44
46
  end
45
- path = File.join(@cache_dir, file_name[0].downcase, file_name)
47
+ path = target_file
48
+ else
49
+ @log.info("Filesystem cache HIT for path: #{relative_path}")
46
50
  end
47
51
  File.open(path, 'rb')
48
52
  rescue => e
@@ -59,6 +63,27 @@ module PuppetForgeServer::Backends
59
63
  protected
60
64
  attr_reader :log
61
65
 
66
+ def get_non_mutable(relative_url)
67
+ file_name = relative_url.split('/').last
68
+ target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
69
+ path = Dir["#{@cache_dir}/**/#{file_name}"].first
70
+ unless File.exist?("#{path}")
71
+ buffer = get(relative_url)
72
+ File.open(target_file, 'wb') do |file|
73
+ file.write(buffer)
74
+ @log.debug("Saved #{buffer} bytes in filesystem cache for url: #{relative_url}, target file: #{target_file}")
75
+ end
76
+ path = target_file
77
+ else
78
+ @log.info("Filesystem cache HIT for url: #{relative_url}")
79
+ end
80
+ File.binread(path)
81
+ rescue => e
82
+ @log.error("#{self.class.name} failed getting non-mutable url '#{relative_url}'")
83
+ @log.error("Error: #{e}")
84
+ return nil
85
+ end
86
+
62
87
  def get(relative_url)
63
88
  @http_client.get(url(relative_url))
64
89
  end
@@ -30,11 +30,11 @@ module PuppetForgeServer::Backends
30
30
  def get_metadata(author, name, options = {})
31
31
  query ="#{author}-#{name}"
32
32
  begin
33
- releases = options[:version] ? [JSON.parse(get("/v3/releases/#{query}-#{options[:version]}"))] : get_all_result_pages("/v3/releases?module=#{query}")
33
+ releases = get_releases(query, options)
34
34
  get_modules(releases)
35
35
  rescue => e
36
- @log.debug("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
37
- @log.debug("Error: #{e}")
36
+ @log.error("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
37
+ @log.error("Error: #{e}")
38
38
  return nil
39
39
  end
40
40
  end
@@ -44,13 +44,26 @@ module PuppetForgeServer::Backends
44
44
  releases = get_all_result_pages("/v3/modules?query=#{query}").map {|element| element['current_release']}
45
45
  get_modules(releases)
46
46
  rescue => e
47
- @log.debug("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
48
- @log.debug("Error: #{e}")
47
+ @log.error("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
48
+ @log.error("Error: #{e}")
49
49
  return nil
50
50
  end
51
51
  end
52
52
 
53
53
  private
54
+
55
+ def get_releases(query, options = {})
56
+ version = options[:version]
57
+ unless version.nil?
58
+ url = "/v3/releases/#{query}-#{version}"
59
+ buffer = get_non_mutable(url)
60
+ release = JSON.parse(buffer)
61
+ [ release ]
62
+ else
63
+ get_all_result_pages("/v3/releases?module=#{query}")
64
+ end
65
+ end
66
+
54
67
  def get_all_result_pages(next_page)
55
68
  results = []
56
69
  begin
@@ -80,7 +93,8 @@ module PuppetForgeServer::Backends
80
93
  :checksum => element['file_md5'],
81
94
  :path => element['file_uri'].gsub(/^#{@@FILE_PATH}/, ''),
82
95
  :tags => (element['tags'] + (element['metadata']['tags'] ? element['metadata']['tags'] : [])).flatten.uniq,
83
- :deleted_at => element['deleted_at']
96
+ :deleted_at => element['deleted_at'],
97
+ :readme => element['readme']
84
98
  })
85
99
  end
86
100
  end
@@ -23,6 +23,15 @@ require 'net/http/post/multipart'
23
23
 
24
24
  module PuppetForgeServer::Http
25
25
  class HttpClient
26
+ include PuppetForgeServer::Utils::CacheProvider
27
+ include PuppetForgeServer::Utils::FilteringInspecter
28
+
29
+ def initialize(cache = nil)
30
+ cache = cache_instance if cache.nil?
31
+ cache.extend(PuppetForgeServer::Utils::FilteringInspecter)
32
+ @log = PuppetForgeServer::Logger.get
33
+ @cache = cache
34
+ end
26
35
 
27
36
  def post_file(url, file_hash, options = {})
28
37
  options = { :http => {}, :headers => {}}.merge(options)
@@ -45,11 +54,29 @@ module PuppetForgeServer::Http
45
54
  open_uri(url)
46
55
  end
47
56
 
57
+ def inspect
58
+ cache_inspected = @cache.inspect_without [ :@data ]
59
+ cache_inspected.gsub!(/>$/, ", @size=#{@cache.size}>")
60
+ inspected = inspect_without [ :@cache ]
61
+ inspected.gsub(/>$/, ", @cache=#{cache_inspected}>")
62
+ end
63
+
48
64
  private
65
+
49
66
  def open_uri(url)
50
- ::Timeout.timeout(10) do
51
- open(url, 'User-Agent' => "Puppet-Forge-Server/#{PuppetForgeServer::VERSION}", :allow_redirections => :safe)
67
+ hit_or_miss = @cache.include?(url) ? 'HIT' : 'MISS'
68
+ @log.info "Cache in RAM memory size: #{@cache.size}, #{hit_or_miss} for url: #{url}"
69
+ contents = @cache.fetch(url) do
70
+ tmpfile = ::Timeout.timeout(10) do
71
+ PuppetForgeServer::Logger.get.debug "Fetching data for url: #{url} from remote server"
72
+ open(url, 'User-Agent' => "Puppet-Forge-Server/#{PuppetForgeServer::VERSION}", :allow_redirections => :safe)
73
+ end
74
+ contents = tmpfile.read
75
+ tmpfile.close
76
+ contents
52
77
  end
78
+ @log.debug "Data for url: #{url} fetched, #{contents.size} bytes"
79
+ StringIO.new(contents)
53
80
  end
54
81
  end
55
82
  end
@@ -16,6 +16,7 @@
16
16
 
17
17
 
18
18
  require 'logger'
19
+ require 'logger/colors'
19
20
 
20
21
  module PuppetForgeServer
21
22
  class Logger
@@ -45,7 +46,13 @@ module PuppetForgeServer
45
46
  else
46
47
  method_name
47
48
  end
48
- @loggers.each { |logger| logger.send(method_name, args.first) }
49
+ if args.size > 0
50
+ # setters
51
+ @loggers.each { |logger| logger.send(method_name, args.first) }
52
+ else
53
+ # getters
54
+ @loggers.collect { |logger| logger.send(method_name) }
55
+ end
49
56
  end
50
57
 
51
58
  def respond_to?(method_name, include_private = false)
@@ -70,4 +77,4 @@ module PuppetForgeServer
70
77
  end
71
78
  end
72
79
  end
73
- end
80
+ end
@@ -27,6 +27,11 @@ module PuppetForgeServer::Models
27
27
  end
28
28
 
29
29
  def to_hash(obj = self)
30
+ # Quickly return if it's not one of ours
31
+ unless obj.kind_of? Builder
32
+ return obj
33
+ end
34
+ # Otherwise start building hash
30
35
  hash = {}
31
36
  obj.instance_variables.each do |var|
32
37
  var_value = obj.instance_variable_get(var)
@@ -41,4 +46,4 @@ module PuppetForgeServer::Models
41
46
  hash
42
47
  end
43
48
  end
44
- end
49
+ end
@@ -44,4 +44,4 @@ module PuppetForgeServer::Models
44
44
  @version.eql?(other.version)
45
45
  end
46
46
  end
47
- end
47
+ end
@@ -18,7 +18,7 @@
18
18
  module PuppetForgeServer::Models
19
19
  class Module < Builder
20
20
 
21
- attr_accessor :metadata, :checksum, :path, :private, :deleted_at, :tags
21
+ attr_accessor :metadata, :checksum, :path, :private, :deleted_at, :tags, :readme
22
22
 
23
23
  def initialize(attributes)
24
24
  super(attributes)
@@ -26,4 +26,4 @@ module PuppetForgeServer::Models
26
26
  end
27
27
 
28
28
  end
29
- end
29
+ end
@@ -19,6 +19,7 @@ require 'rack/mount'
19
19
  module PuppetForgeServer
20
20
  class Server
21
21
  include PuppetForgeServer::Utils::OptionParser
22
+ include PuppetForgeServer::Utils::CacheProvider
22
23
  include PuppetForgeServer::Utils::Http
23
24
 
24
25
  def go(args)
@@ -27,6 +28,7 @@ module PuppetForgeServer
27
28
  begin
28
29
  options = parse_options(args)
29
30
  @log = logging(options)
31
+ configure_cache(options[:ram_cache_ttl], options[:ram_cache_size])
30
32
  backends = backends(options)
31
33
  server = build(backends, options[:webui_root])
32
34
  announce(options, backends)
@@ -22,9 +22,11 @@ module PuppetForgeServer::Utils
22
22
  def read_from_archive(archive, name_regex)
23
23
  tar = Gem::Package::TarReader.new(Zlib::GzipReader.open(archive))
24
24
  tar.rewind
25
+ files = {}
25
26
  tar.each do |obj|
26
- return obj.read if obj.full_name =~ name_regex
27
+ files[obj.full_name] = obj.read if obj.file? && obj.full_name =~ name_regex
27
28
  end
29
+ return files unless files.empty?
28
30
  raise "Given name #{name_regex} not found in #{archive}"
29
31
  end
30
32
  end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2015 Centralny Osroder Informatyki (gov.pl)
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lrucache'
18
+
19
+ module PuppetForgeServer::Utils
20
+ module CacheProvider
21
+
22
+ opts = PuppetForgeServer::Utils::OptionParser.DEFAULT_OPTIONS
23
+ @@CACHE = LRUCache.new(:ttl => opts[:ram_cache_ttl], :max_size => opts[:ram_cache_size])
24
+
25
+ # Method for fetching application wide cache for fetching HTTP requests
26
+ #
27
+ # @return [LRUCache] a instance of cache for application
28
+ def cache_instance
29
+ @@CACHE
30
+ end
31
+
32
+ # Configure a application wide cache using LSUCache implementation
33
+ #
34
+ # @param [int] ttl a time to live for elements
35
+ # @param [int] size a maximum size for cache
36
+ def configure_cache(ttl, size)
37
+ @@CACHE = LRUCache.new(:ttl => ttl, :max_size => size)
38
+ PuppetForgeServer::Logger.get.info("Using RAM memory LRUCache with time to live of #{ttl}sec and max size of #{size} elements")
39
+ nil
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2015 Centralny Osroder Informatyki (gov.pl)
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'iconv'
18
+
19
+ module PuppetForgeServer::Utils
20
+ module Encoding
21
+
22
+ @@ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
23
+
24
+ # Converts give text to valid UTF-8
25
+ # @param [string] text given string, can be null
26
+ # @return [string] output string in utf-8
27
+ def to_utf8(text)
28
+ replaced = text
29
+ unless replaced.nil?
30
+ replaced = replaced.force_encoding("UTF-8") if is_ascii_8bit?(replaced)
31
+ replaced = cleanup_utf8(replaced)
32
+ end
33
+ replaced
34
+ end
35
+
36
+ private
37
+
38
+ def is_ascii_8bit?(text)
39
+ text.encoding.name == 'ASCII-8BIT'
40
+ end
41
+
42
+ def cleanup_utf8(text)
43
+ @@ic.iconv(text)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2015 Centralny Osroder Informatyki (gov.pl)
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module PuppetForgeServer::Utils
18
+ module FilteringInspecter
19
+ def self.inspect_without(object, variables)
20
+ filtered = object.instance_variables.reject { |n| variables.include? n }
21
+ vars = filtered.map { |n| "#{n}=#{object.instance_variable_get(n).inspect}" }
22
+ oid = object.object_id << 1
23
+ "#<%s:0x%x %s>" % [ object.class, oid, vars.join(', ') ]
24
+ end
25
+
26
+ def inspect_without(variables)
27
+ PuppetForgeServer::Utils::FilteringInspecter.inspect_without(self, variables)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Copyright 2015 North Development AB
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'tilt/redcarpet'
18
+
19
+ module PuppetForgeServer::Utils
20
+ module MarkdownRenderer
21
+
22
+ class CustomRenderer < Redcarpet::Render::HTML
23
+ def block_code(code, lang)
24
+ output = "<pre>"
25
+ output << "<code class=\"prettyprint lang-puppet\">#{code}</code>"
26
+ output << "</pre>"
27
+ end
28
+ end
29
+
30
+ def markdown(text)
31
+ options = {
32
+ filter_html: true,
33
+ with_toc_data: true,
34
+ hard_wrap: true,
35
+ prettify: true
36
+ }
37
+
38
+ extensions = {
39
+ autolink: true,
40
+ superscript: true,
41
+ disable_indented_code_blocks: false,
42
+ fenced_code_blocks: true,
43
+ strikethrough: true,
44
+ quote: true,
45
+ tables: true
46
+ }
47
+
48
+ renderer = CustomRenderer.new(options)
49
+ markdown = Redcarpet::Markdown.new(renderer, extensions)
50
+ markdown.render(text)
51
+ end
52
+
53
+ end
54
+ end