knife-art 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []