knife-art 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c19783dc7672ed6332889e731bc7381e4cbb63d6
4
+ data.tar.gz: '0903aec7e346817090c7021a3ef5334f5d29f99f'
5
+ SHA512:
6
+ metadata.gz: d37ef408132931d8bafeb96921e6ab7a742b34df5e55c74e4dc8017713897fb5a7688705b92b06ec9bc1e9769d5d917c0ec63b48d565a48dd3bf073492ddd68c
7
+ data.tar.gz: 7db7629d2ddfd2e75be70130d49f96b7a71e9d60645759543bc3eaa2f57e36a9643e4ea0ce32fcaa89eb8d5048be579b505fe6f2a4a79c0dcd97aff66f0b2740
@@ -0,0 +1,16 @@
1
+
2
+ require 'chef/knife'
3
+ require 'chef/knife/cookbook_site_show'
4
+
5
+ class Chef
6
+ class Knife
7
+ class ArtifactoryShow < Knife::CookbookSiteShow
8
+
9
+ dependency_loaders.concat(superclass.dependency_loaders)
10
+ options.merge!(superclass.options)
11
+
12
+ banner "knife artifactory show COOKBOOK [VERSION] (options)"
13
+ category "artifactory"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,79 @@
1
+ # Overrides the default Chef::Knife::CookbookSiteShare to allow basic authentication against an Artifactory backend.
2
+ # Ideally we would like to use a mechanism that allows injecting pluggable authentication middleware into the Chef::Http
3
+ # REST clients, but in the interest of allowing not-only-newest knife client versions to work with Artifactory we chose
4
+ # this solution for now.
5
+
6
+ require 'chef/knife'
7
+ require 'chef/knife/cookbook_site_download'
8
+ require 'knife-art/knife_art_utils'
9
+
10
+ class Chef
11
+ class Knife
12
+ class ArtifactoryDownload < Knife::CookbookSiteDownload
13
+
14
+ dependency_loaders.concat(superclass.dependency_loaders)
15
+ options.merge!(superclass.options)
16
+
17
+ banner "knife artifactory download COOKBOOK [VERSION] (options)"
18
+ category "artifactory"
19
+
20
+ alias_method :orig_run, :run
21
+ alias_method :orig_download_cookbook, :download_cookbook
22
+ alias_method :orig_current_cookbook_data, :current_cookbook_data
23
+ alias_method :orig_desired_cookbook_data, :desired_cookbook_data
24
+
25
+ def run
26
+ config[:artifactory_download] = true
27
+ Chef::Log.debug("[KNIFE-ART] running site download with config: #{config}")
28
+ orig_run
29
+ end
30
+
31
+ private
32
+
33
+ def current_cookbook_data
34
+ unless config[:artifactory_download]
35
+ Chef::Log.debug('[KNIFE-ART] ArtifactoryDownload::current_cookbook_data called without artifactory flag, delegating to super')
36
+ return orig_current_cookbook_data
37
+ end
38
+ @current_cookbook_data ||= begin
39
+ noauth_rest.get("#{cookbooks_api_url}/#{@name_args[0]}", auth_header)
40
+ end
41
+ end
42
+
43
+ def desired_cookbook_data
44
+ unless config[:artifactory_download]
45
+ Chef::Log.debug('[KNIFE-ART] ArtifactoryDownload::desired_cookbook_data called without artifactory flag, delegating to super')
46
+ return orig_desired_cookbook_data
47
+ end
48
+ @desired_cookbook_data ||= begin
49
+ uri = if @name_args.length == 1
50
+ current_cookbook_data["latest_version"]
51
+ else
52
+ specific_cookbook_version_url
53
+ end
54
+
55
+ noauth_rest.get(uri, auth_header)
56
+ end
57
+ end
58
+
59
+ def download_cookbook
60
+ unless config[:artifactory_download]
61
+ Chef::Log.debug('[KNIFE-ART] ArtifactoryDownload::download_cookbook called without artifactory flag, delegating to super')
62
+ return orig_download_cookbook
63
+ end
64
+ ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
65
+ tf = noauth_rest.streaming_request(desired_cookbook_data["file"], auth_header)
66
+
67
+ ::FileUtils.cp tf.path, download_location
68
+ ui.info "Cookbook saved: #{download_location}"
69
+ end
70
+
71
+ def auth_header
72
+ @auth_header ||= begin
73
+ ::Knife::KnifeArt::KnifeArtUtils.auth_header_from(cookbooks_api_url)
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,46 @@
1
+ # Overrides the default Chef::Knife::CookbookSiteInstall to allow basic authentication against an Artifactory backend.
2
+ # Ideally we would like to use a mechanism that allows injecting pluggable authentication middleware into the Chef::Http
3
+ # REST clients, but in the interest of allowing not-only-newest knife client versions to work with Artifactory we chose
4
+ # this solution for now.
5
+
6
+
7
+ require 'chef/knife'
8
+ require 'chef/knife/cookbook_site_install'
9
+
10
+ class Chef
11
+ class Knife
12
+ class ArtifactoryInstall < Knife::CookbookSiteInstall
13
+
14
+ dependency_loaders.concat(superclass.dependency_loaders)
15
+ options.merge!(superclass.options)
16
+
17
+ banner "knife artifactory install COOKBOOK [VERSION] (options)"
18
+ category "artifactory"
19
+
20
+ alias_method :orig_run, :run
21
+ alias_method :orig_download_cookbook_to, :download_cookbook_to
22
+
23
+ def run
24
+ config[:artifactory_install] = true
25
+ Chef::Log.debug("[KNIFE-ART] running site install with config: #{config}")
26
+ orig_run
27
+ end
28
+
29
+ private
30
+
31
+ def download_cookbook_to(download_path)
32
+ unless config[:artifactory_install]
33
+ Chef::Log.debug('[KNIFE-ART] ArtifactoryInstall::download_cookbook_to called without artifactory flag, delegating to super')
34
+ return orig_download_cookbook_to(download_path)
35
+ end
36
+ downloader = Chef::Knife::ArtifactoryDownload.new
37
+ downloader.config[:file] = download_path
38
+ downloader.config[:supermarket_site] = config[:supermarket_site]
39
+ downloader.name_args = name_args
40
+ downloader.run
41
+ downloader
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+
2
+ require 'chef/knife'
3
+ require 'chef/knife/cookbook_site_list'
4
+
5
+ class Chef
6
+ class Knife
7
+ class ArtifactoryList < Knife::CookbookSiteList
8
+
9
+ dependency_loaders.concat(superclass.dependency_loaders)
10
+ options.merge!(superclass.options)
11
+
12
+ banner "knife artifactory list (options)"
13
+ category "artifactory"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+
2
+ require 'chef/knife'
3
+ require 'chef/knife/cookbook_site_search'
4
+
5
+ class Chef
6
+ class Knife
7
+ class ArtifactorySearch < Knife::CookbookSiteSearch
8
+
9
+ dependency_loaders.concat(superclass.dependency_loaders)
10
+ options.merge!(superclass.options)
11
+
12
+ banner "knife artifactory search QUERY (options)"
13
+ category "artifactory"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,117 @@
1
+ # The purpose of this class is double:
2
+ # 1. allow passing flags to the super class so that the do_upload method of this
3
+ # class is called and that signing key verification is skipped by the underlying Chef::HTTP::Authenticator that's
4
+ # used with the rest client (see comment below).
5
+ # 2. Override (monkey patch) the required methods in Chef::HTTP::Authenticator and Knife::CookbookSiteShare
6
+ # to allow inserting our own logic that deploys a cookbook to Artifactory.
7
+ #
8
+ # The Supermarket API is kept (post /api/v1/cookbooks/cookbook_name) by Artifactory although it does not currently
9
+ # return a correct response (it simply returns 200) due to performance considerations.
10
+
11
+
12
+ require 'chef/knife'
13
+ require 'chef/knife/cookbook_site_share'
14
+
15
+ class Chef
16
+ class Knife
17
+ class ArtifactoryShare < Knife::CookbookSiteShare
18
+
19
+ dependency_loaders.concat(superclass.dependency_loaders)
20
+ options.merge!(superclass.options)
21
+
22
+ banner "knife artifactory share COOKBOOK [CATEGORY] (options)"
23
+ category "artifactory"
24
+
25
+ alias_method :orig_run, :run
26
+ alias_method :orig_get_category, :get_category
27
+ alias_method :orig_do_upload, :do_upload
28
+
29
+ def run
30
+ begin
31
+ # I'm forced to use threadlocal until we find a better solution... can't really find a way to pass configuration
32
+ # down to the Chef::CookbookUploader, Chef::ServerAPI, Chef::HTTP or Chef::HTTP::Authenticator
33
+ # (which are created one after another starting) with CookbookUploader to make it skip the signing key verification.
34
+ # Can make the authenticator skip by passing load_signing_key(nil, nil) and opts[:sign_request] => false
35
+ Thread.current[:artifactory_deploy] = 'yes'
36
+ # Send artifactory deploy flag to super
37
+ config[:artifactory_deploy] = true
38
+ Chef::Log.debug("[KNIFE-ART] running site share with config: #{config}")
39
+ orig_run
40
+ ensure
41
+ # always cleanup threadlocal
42
+ Thread.current[:artifactory_deploy] = nil
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Pretty much copy paste of the original, just with authentication on the rest client...
49
+ def get_category(cookbook_name)
50
+ # Use Artifactory deployment logic only if flag sent by Artifactory plugin
51
+ unless config[:artifactory_deploy]
52
+ Chef::Log.debug('[KNIFE-ART] ArtifactoryShare::get_category called without artifactory flag, delegating to super')
53
+ return orig_get_category(cookbook_name)
54
+ end
55
+ begin
56
+ data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}")
57
+ if data.nil?
58
+ return data["category"]
59
+ else
60
+ return 'Other'
61
+ end
62
+ rescue => e
63
+ return "Other" if e.kind_of?(Net::HTTPServerException) && e.response.code == "404"
64
+ ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
65
+ Chef::Log.debug("\n#{e.backtrace.join("\n")}")
66
+ exit(1)
67
+ end
68
+
69
+ end
70
+
71
+ def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
72
+ # Use Artifactory deployment logic only if flag sent by Artifactory plugin
73
+ unless config[:artifactory_deploy]
74
+ Chef::Log.debug('[KNIFE-ART] ArtifactoryShare::do_upload called without artifactory flag, delegating to super')
75
+ orig_do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
76
+ return
77
+ end
78
+ # cookbook_filename is set as tempDir/cookbook_name in parent
79
+ cookbook_name = cookbook_filename.split('/')[-1]
80
+ uri = "#{config[:supermarket_site]}/api/v1/cookbooks/#{cookbook_name}"
81
+ uri += "?category=#{cookbook_category}" if cookbook_category
82
+ Chef::Log.debug("[KNIFE-ART] Deploying cookbook #{cookbook_name} to Artifactory url at #{uri}")
83
+ # This guy throws an exception and consumes the request body upon non-ok http code, and deprives us of the
84
+ # ability to do anything with the response itself... i'm letting the parent catch it and terminate.
85
+ # debug log will be able to show the response Artifactory returned in case of errors.
86
+ file_contents = File.open(cookbook_filename, "rb") { |f| f.read }
87
+ # no need to send auth header here, 'normal' HTTP client uses url with credentials from config
88
+ rest.post(uri, file_contents, {"content-type" => "application/x-binary"})
89
+ end
90
+
91
+ end
92
+ end
93
+ end
94
+
95
+ # Chef::Http::Authenticator monkeypatch to allow skipping signing key verification when deploying to Artifactory
96
+ class Chef
97
+ class HTTP
98
+ class Authenticator
99
+
100
+ alias_method :orig_load_signing_key, :load_signing_key
101
+
102
+ def load_signing_key(key_file, raw_key = nil)
103
+ Chef::Log.debug("[KNIFE-ART] global var: #{Thread.current[:artifactory_deploy]}")
104
+ if Thread.current.key?(:artifactory_deploy) and Thread.current[:artifactory_deploy].eql? 'yes'
105
+ Chef::Log.debug('[KNIFE-ART] Artifactory plugin substituting for Chef::Http::Authenticator --> omitting signing key usage')
106
+ @sign_request = false
107
+ @raw_key = ''
108
+ @key = ''
109
+ else
110
+ # Artifactory flag not present, call original implementation
111
+ orig_load_signing_key(key_file, raw_key)
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,66 @@
1
+ # More or less copy-pasted from cookbook_site_unshare because the http call happens inside the run method, not much
2
+ # sense in extending it
3
+
4
+ require 'chef/knife'
5
+
6
+ class Chef
7
+ class Knife
8
+ class ArtifactoryUnshare < Knife
9
+
10
+ deps do
11
+ require "chef/json_compat"
12
+ end
13
+
14
+ banner "knife artifactory unshare COOKBOOK VERSION"
15
+ category "artifactory"
16
+
17
+ option :supermarket_site,
18
+ :short => "-m SUPERMARKET_SITE",
19
+ :long => "--supermarket-site SUPERMARKET_SITE",
20
+ :description => "Supermarket Site",
21
+ :default => "https://supermarket.chef.io",
22
+ :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
23
+
24
+ def run
25
+ @cookbook_name = @name_args[0]
26
+ if @cookbook_name.nil?
27
+ show_usage
28
+ ui.fatal "You must provide the name of the cookbook to unshare"
29
+ exit 1
30
+ end
31
+ @cookbook_version = @name_args[1]
32
+ if @cookbook_version.nil?
33
+ show_usage
34
+ ui.fatal "You must provide a version to unshare"
35
+ exit 1
36
+ end
37
+
38
+ confirm "Are you sure you want to delete version #{@cookbook_version} of the cookbook #{@cookbook_name} from Artifactory"
39
+
40
+ begin
41
+ url = "#{cookbooks_api_url}/#{@cookbook_name}/#{@cookbook_version}"
42
+ noauth_rest.delete(url, auth_header)
43
+ rescue Net::HTTPServerException => e
44
+ raise e unless (e.message =~ /Forbidden/ || e.message =~ /Unauthorized/)
45
+ ui.error "Forbidden: You must have delete permissions on the target repo to delete #{@cookbook_name}."
46
+ exit 1
47
+ end
48
+
49
+ ui.info "Deleted version #{@cookbook_version} of the cookbook #{@cookbook_name}"
50
+ end
51
+
52
+ private
53
+
54
+ def cookbooks_api_url
55
+ "#{config[:supermarket_site]}/api/v1/cookbooks"
56
+ end
57
+
58
+ def auth_header
59
+ @auth_header ||= begin
60
+ ::Knife::KnifeArt::KnifeArtUtils.auth_header_from(cookbooks_api_url)
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,26 @@
1
+ require 'base64'
2
+
3
+ module Knife
4
+ module KnifeArt
5
+ class KnifeArtUtils
6
+
7
+ def self.auth_header_from(uri)
8
+ Chef::Log.debug("[KNIFE-ART] in util, got url: #{uri}")
9
+ begin
10
+ url = URI.parse(uri.gsub(%r{/+$}, ""))
11
+ Chef::Log.debug("[KNIFE-ART] in util, parsed url: #{uri}")
12
+ if url.user and url.password
13
+ user = URI.unescape(url.user)
14
+ password = URI.unescape(url.password)
15
+ return {"Authorization" => "Basic " + Base64.strict_encode64("#{user}:#{password}")}
16
+ end
17
+ {}
18
+ end
19
+ rescue Exception => e
20
+ Chef::Log.warn("[KNIFE-ART] Unable to parse url: #{uri} --> #{e.message}")
21
+ {}
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module KnifeArt
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-art
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Feldman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Enables basic authentication support for share and upload operations
14
+ to Artifactory when it serves as a Supermarket.
15
+ email:
16
+ - art-dev@jfrog.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/chef/knife/artifactory_show.rb
22
+ - lib/chef/knife/knife_art_download.rb
23
+ - lib/chef/knife/knife_art_install.rb
24
+ - lib/chef/knife/knife_art_list.rb
25
+ - lib/chef/knife/knife_art_search.rb
26
+ - lib/chef/knife/knife_art_share.rb
27
+ - lib/chef/knife/knife_art_unshare.rb
28
+ - lib/knife-art/knife_art_utils.rb
29
+ - lib/knife-art/version.rb
30
+ homepage: https://github.com/JFrogDev/knife-art
31
+ licenses:
32
+ - Apache-2.0
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 2.6.8
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Artifactory integration for Knife
54
+ test_files: []