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.
- checksums.yaml +7 -0
- data/lib/chef/knife/artifactory_show.rb +16 -0
- data/lib/chef/knife/knife_art_download.rb +79 -0
- data/lib/chef/knife/knife_art_install.rb +46 -0
- data/lib/chef/knife/knife_art_list.rb +16 -0
- data/lib/chef/knife/knife_art_search.rb +16 -0
- data/lib/chef/knife/knife_art_share.rb +117 -0
- data/lib/chef/knife/knife_art_unshare.rb +66 -0
- data/lib/knife-art/knife_art_utils.rb +26 -0
- data/lib/knife-art/version.rb +3 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -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
|
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: []
|