knife-artifactory 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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-artifactory/utils.rb +24 -0
- data/lib/knife-artifactory/version.rb +3 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d57af63a2e4d97bd447477bd0ae1a3fe8dd4e10fff29f98e249a2c3945aa2b28
|
4
|
+
data.tar.gz: 9deb1ef44897e7c12fe6a0ec4e440809d55df05a70d662f1008d1734a45447e2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abff25d20f732130844ad178c00cf8a7149251220c3efd470405dd4111692812d8b31f6916a251978a7a772548beb3596244cf09b3fa5aba731aaed82fa01d8b
|
7
|
+
data.tar.gz: 11575c085633e4bfd13fecd9a39e00a4728dee1fed23a5107224b388afc7e33f9d6d4ab2baeb8bf5025d2d97c620e8706e8360c41e6f233fde41bfdf15e566af
|
@@ -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-artifactory/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,24 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module KnifeArtifactory
|
4
|
+
class Utils
|
5
|
+
|
6
|
+
def self.auth_header_from(uri)
|
7
|
+
Chef::Log.debug("[KNIFE-ART] in util, got url: #{uri}")
|
8
|
+
begin
|
9
|
+
url = URI.parse(uri.gsub(%r{/+$}, ""))
|
10
|
+
Chef::Log.debug("[KNIFE-ART] in util, parsed url: #{uri}")
|
11
|
+
if url.user and url.password
|
12
|
+
user = URI.unescape(url.user)
|
13
|
+
password = URI.unescape(url.password)
|
14
|
+
return {"Authorization" => "Basic " + Base64.strict_encode64("#{user}:#{password}")}
|
15
|
+
end
|
16
|
+
{}
|
17
|
+
end
|
18
|
+
rescue Exception => e
|
19
|
+
Chef::Log.warn("[KNIFE-ART] Unable to parse url: #{uri} --> #{e.message}")
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife-artifactory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Feldman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-23 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-artifactory/utils.rb
|
29
|
+
- lib/knife-artifactory/version.rb
|
30
|
+
homepage: https://github.com/jasonwbarnett/knife-artifactory
|
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
|
+
rubygems_version: 3.0.3
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: Artifactory integration for Knife
|
53
|
+
test_files: []
|