foreman_docker 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -1
- data/app/assets/javascripts/foreman_docker/container_image_search.js +203 -0
- data/app/assets/javascripts/foreman_docker/create_registry.js +15 -0
- data/app/assets/stylesheets/foreman_docker/autocomplete.css.scss +4 -0
- data/app/controllers/concerns/foreman/controller/parameters/docker_registry.rb +1 -1
- data/app/controllers/image_search_controller.rb +6 -35
- data/app/helpers/container_steps_helper.rb +0 -1
- data/app/models/container.rb +1 -1
- data/app/models/docker_container_wizard_state.rb +1 -1
- data/app/models/docker_container_wizard_states/configuration.rb +1 -1
- data/app/models/docker_container_wizard_states/environment.rb +1 -1
- data/app/models/docker_container_wizard_states/image.rb +1 -1
- data/app/models/docker_container_wizard_states/preliminary.rb +1 -1
- data/app/models/docker_parameter.rb +1 -1
- data/app/models/docker_registry.rb +8 -4
- data/app/models/foreman_docker/docker.rb +10 -2
- data/app/models/service/registry_api.rb +10 -5
- data/app/views/containers/steps/_form_buttons.html.erb +5 -7
- data/app/views/containers/steps/_image_hub_tab.html.erb +16 -17
- data/app/views/containers/steps/image.html.erb +1 -3
- data/app/views/image_search/_repository_search_results.html.erb +1 -1
- data/app/views/registries/_form.html.erb +5 -3
- data/app/views/registries/index.html.erb +11 -10
- data/db/migrate/20170508130316_add_verify_ssl_option_to_docker_registries.rb +5 -0
- data/lib/foreman_docker/engine.rb +7 -6
- data/lib/foreman_docker/version.rb +1 -1
- data/test/functionals/image_search_controller_test.rb +120 -169
- data/test/integration/container_steps_test.rb +118 -18
- data/test/integration/registry_creation_test.rb +19 -0
- data/test/units/docker_container_wizard_states/image_test.rb +0 -1
- data/test/units/docker_registry_test.rb +0 -8
- data/test/units/registry_api_test.rb +13 -1
- metadata +10 -6
- data/app/assets/javascripts/foreman_docker/image_step.js +0 -175
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c16b711b658dfe65317085cac2406cb5797cb7eb
|
4
|
+
data.tar.gz: d895b63f9b5a822e9e9124a2ddc07acac9008860
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bdd5e1a7cd1807ff3f8273e869110f3c4310cf1d39184e195be90ccb73552d5478d1b4c11942d3c7301ad5be7ff6c70ccb1edfbd147f5bc2889de6026cc151b
|
7
|
+
data.tar.gz: 5d245182ed0511904f31e49979ae45b239e53563c8bd4adca5902d416a2130424e4cd473693a2b0bf4487914649e5c9966b522ca9d80f8bc5ad14ca3814c02f3
|
data/README.md
CHANGED
@@ -86,6 +86,16 @@ That's it. You're now ready to create and manage containers in your new Docker c
|
|
86
86
|
| >= 1.6 | 0.1.0 - 0.2.0 |
|
87
87
|
| >= 1.7 | 1.0.0 - 2.1.1 |
|
88
88
|
| >= 1.7 | 3.0.0+ |
|
89
|
+
| >= 1.15 | 3.1.0+ |
|
90
|
+
|
91
|
+
## Docker Registry API Compatibility
|
92
|
+
|
93
|
+
| Plugin version | Registry API version |
|
94
|
+
| ---------------:| --------------:|
|
95
|
+
| < 3.1.0 | [v1](http://docs.master.dockerproject.org/v1.7/reference/api/registry_api/) |
|
96
|
+
| >= 3.1.0 | [v1](http://docs.master.dockerproject.org/v1.7/reference/api/registry_api/), [v2](https://docs.docker.com/registry/spec/api/)***** |
|
97
|
+
|
98
|
+
_*** Note:** API v2 as default and v1 to fall back on._
|
89
99
|
|
90
100
|
See extras/RELEASE.md for more detailed information on compatibility and releases.
|
91
101
|
|
@@ -124,4 +134,3 @@ GNU General Public License for more details.
|
|
124
134
|
|
125
135
|
You should have received a copy of the GNU General Public License
|
126
136
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
127
|
-
|
@@ -0,0 +1,203 @@
|
|
1
|
+
function ContainerImageSearch() {
|
2
|
+
this.initialize = function (registryType) {
|
3
|
+
this.registryType = registryType;
|
4
|
+
this.form = $('form[data-registry="' + this.registryType + '"]');
|
5
|
+
this.inputs = {
|
6
|
+
image: this.getInput('image'),
|
7
|
+
tag: this.getInput('tag')
|
8
|
+
};
|
9
|
+
this.results = this.form.find('.registry-search-results')
|
10
|
+
this.resultsList = this.results.find('.registry-search-results-list');
|
11
|
+
this.resultsList.on('click', this.selectImage.bind(this));
|
12
|
+
|
13
|
+
this.searchButton = this.form.find('.image-search-button');
|
14
|
+
this.searchButton.on('click', function (event) {
|
15
|
+
event.preventDefault();
|
16
|
+
this.fullResultList();
|
17
|
+
}.bind(this))
|
18
|
+
|
19
|
+
this.setupInputs();
|
20
|
+
};
|
21
|
+
|
22
|
+
this.registryId = function () {
|
23
|
+
return $('#docker_container_wizard_states_image_registry_id').val();
|
24
|
+
};
|
25
|
+
|
26
|
+
this.getInput = function (input) {
|
27
|
+
return this.form.find('input[data-' + input + ']:first');
|
28
|
+
};
|
29
|
+
|
30
|
+
this.getFormGroup = function (input) {
|
31
|
+
return input.closest('.form-group');
|
32
|
+
}
|
33
|
+
|
34
|
+
this.getSpinner = function (input) {
|
35
|
+
return this.getFormGroup(input).find('.autocomplete-status');
|
36
|
+
};
|
37
|
+
|
38
|
+
this.getInlineHelp = function (input) {
|
39
|
+
return this.getFormGroup(input).find('.help-inline');
|
40
|
+
}
|
41
|
+
|
42
|
+
this.validRequest = function () {
|
43
|
+
return (this.registryType == 'registry' && this.registryId() != '' ||
|
44
|
+
this.registryType == 'hub' && this.registryId() == '') &&
|
45
|
+
this.inputs.image.val() != '';
|
46
|
+
}
|
47
|
+
|
48
|
+
this.getAutocompleteResults = function (tag, input, callback, params) {
|
49
|
+
if(!this.validRequest())
|
50
|
+
return;
|
51
|
+
|
52
|
+
var spinner = this.getSpinner(tag),
|
53
|
+
imageName = this.inputs.image.val(),
|
54
|
+
tagsOnly = tag.data('tag'),
|
55
|
+
params = $.extend({
|
56
|
+
registry: this.registryType,
|
57
|
+
search: tagsOnly ? imageName + ':' + input.term : input.term,
|
58
|
+
registry_id: this.registryId(),
|
59
|
+
tags: tagsOnly
|
60
|
+
}, params)
|
61
|
+
|
62
|
+
spinner.removeClass('pficon pficon-error-circle-o pficon-ok')
|
63
|
+
.addClass('spinner spinner-xs').show();
|
64
|
+
|
65
|
+
$.getJSON(tag.data("url"), params, function (data) {
|
66
|
+
this.setAutocompleteConfirmationStatus(tag, data)
|
67
|
+
callback(data);
|
68
|
+
}.bind(this))
|
69
|
+
.error(function(result) {
|
70
|
+
notify('<p>' + result.responseText + '</p>', 'danger');
|
71
|
+
})
|
72
|
+
};
|
73
|
+
|
74
|
+
this.fullResultList = function (event) {
|
75
|
+
if(!this.validRequest())
|
76
|
+
return;
|
77
|
+
|
78
|
+
var list = this.resultsList,
|
79
|
+
input = this.inputs.image;
|
80
|
+
|
81
|
+
input.autocomplete('disable')
|
82
|
+
list.empty();
|
83
|
+
|
84
|
+
$.ajax({
|
85
|
+
type:'get',
|
86
|
+
dataType:'html',
|
87
|
+
url: this.searchButton.data('url'),
|
88
|
+
data: {
|
89
|
+
registry: this.registryType,
|
90
|
+
search: input.val(),
|
91
|
+
registry_id: this.registryId()
|
92
|
+
},
|
93
|
+
success: function (result) {
|
94
|
+
list.html(result).show();
|
95
|
+
},
|
96
|
+
error: function(result) {
|
97
|
+
notify('<p>' + result.responseText + '</p>', 'danger');
|
98
|
+
}});
|
99
|
+
};
|
100
|
+
|
101
|
+
this.selectImage = function (event) {
|
102
|
+
var link = $(event.target);
|
103
|
+
if (link.hasClass('repository-name')) {
|
104
|
+
event.preventDefault();
|
105
|
+
this.inputs.image
|
106
|
+
.val(link.text())
|
107
|
+
this.inputs.tag.val('').focus();
|
108
|
+
}
|
109
|
+
};
|
110
|
+
|
111
|
+
this.setAutocompleteConfirmationStatus = function (field, results) {
|
112
|
+
var spinner = this.getSpinner(field),
|
113
|
+
inlineHelp = this.getInlineHelp(field),
|
114
|
+
resultType = field.data('tag') ? 'Tag' : 'Image',
|
115
|
+
result = results.filter(function (item) {
|
116
|
+
return item.value == field.val();
|
117
|
+
}),
|
118
|
+
available = result.length > 0;
|
119
|
+
|
120
|
+
inlineHelp.find('.autocomplete-confirmation').remove()
|
121
|
+
spinner.removeClass('spinner spinner-xs pficon-error-circle-o pficon-ok');
|
122
|
+
|
123
|
+
if (field.val() == '')
|
124
|
+
return;
|
125
|
+
|
126
|
+
if (available) {
|
127
|
+
spinner.addClass('pficon pficon-ok');
|
128
|
+
} else {
|
129
|
+
spinner.addClass('pficon pficon-error-circle-o');
|
130
|
+
};
|
131
|
+
|
132
|
+
inlineHelp.append(this.confirmationWrapper(resultType, field.val(), available));
|
133
|
+
};
|
134
|
+
|
135
|
+
this.confirmationWrapper = function(resultType, value, available) {
|
136
|
+
var wrapper = '<span class="autocomplete-confirmation"> ' +
|
137
|
+
resultType + ' <strong>' + value + '</strong> is '
|
138
|
+
|
139
|
+
if (!available)
|
140
|
+
wrapper += '<strong>not</strong>';
|
141
|
+
|
142
|
+
return wrapper + ' available.</span>';
|
143
|
+
};
|
144
|
+
|
145
|
+
this.confirmAutocomplete = function (field, autocomplete) {
|
146
|
+
this.getAutocompleteResults(field, { term: field.val() }, function (results) {
|
147
|
+
this.setAutocompleteConfirmationStatus(field, results)
|
148
|
+
}.bind(this));
|
149
|
+
};
|
150
|
+
|
151
|
+
this.setupAutoCompleteInput = function (field) {
|
152
|
+
var options = $.extend({
|
153
|
+
source: function (input, callback) {
|
154
|
+
this.getAutocompleteResults(field, input, callback)
|
155
|
+
}.bind(this),
|
156
|
+
delay: 500,
|
157
|
+
minLength: field.data('min-length')
|
158
|
+
}, options);
|
159
|
+
|
160
|
+
field.autocomplete(options);
|
161
|
+
|
162
|
+
field.on('blur', function () {
|
163
|
+
this.confirmAutocomplete(field)
|
164
|
+
}.bind(this))
|
165
|
+
};
|
166
|
+
|
167
|
+
this.setupInputs = function () {
|
168
|
+
var image = this.inputs.image,
|
169
|
+
tag = this.inputs.tag;
|
170
|
+
|
171
|
+
this.setupAutoCompleteInput(tag)
|
172
|
+
this.setupAutoCompleteInput(image)
|
173
|
+
|
174
|
+
// Trigger search on pressing enter in image search
|
175
|
+
image.on("keypress", function(e) {
|
176
|
+
if (e.keyCode == 13) {
|
177
|
+
e.preventDefault();
|
178
|
+
this.fullResultList()
|
179
|
+
}
|
180
|
+
}.bind(this))
|
181
|
+
|
182
|
+
image.on('focus', function () {
|
183
|
+
image.autocomplete('enable')
|
184
|
+
});
|
185
|
+
|
186
|
+
tag.on('focus', function () {
|
187
|
+
if (tag.val() == '')
|
188
|
+
tag.autocomplete('search', '');
|
189
|
+
});
|
190
|
+
};
|
191
|
+
|
192
|
+
this.initialize.apply(this, arguments);
|
193
|
+
return this;
|
194
|
+
}
|
195
|
+
|
196
|
+
$(document).ready(function() {
|
197
|
+
var hubSearch = new ContainerImageSearch('hub'),
|
198
|
+
registrySearch = new ContainerImageSearch('registry');
|
199
|
+
|
200
|
+
$('#hub_tab').click( function() {
|
201
|
+
$('#docker_container_wizard_states_image_registry_id').val('');
|
202
|
+
});
|
203
|
+
});
|
@@ -0,0 +1,15 @@
|
|
1
|
+
function toggleVerifySSL () {
|
2
|
+
var urlField = $('#docker_registry_url'),
|
3
|
+
verifySSLField = $('#docker_registry_verify_ssl'),
|
4
|
+
verifySSLFormGroup = verifySSLField.closest('.form-group');
|
5
|
+
|
6
|
+
if (/^https/.test(urlField.val())) {
|
7
|
+
verifySSLFormGroup.show();
|
8
|
+
} else {
|
9
|
+
verifySSLFormGroup.hide();
|
10
|
+
};
|
11
|
+
};
|
12
|
+
|
13
|
+
$(document).ready(function () {
|
14
|
+
$('#docker_registry_url').on('change', toggleVerifySSL)
|
15
|
+
})
|
@@ -4,7 +4,7 @@ module Foreman::Controller::Parameters::DockerRegistry
|
|
4
4
|
class_methods do
|
5
5
|
def docker_registry_params_filter
|
6
6
|
Foreman::ParameterFilter.new(::DockerRegistry).tap do |filter|
|
7
|
-
filter.permit :name, :url, :username, :password, :description,
|
7
|
+
filter.permit :name, :url, :username, :password, :description, :verify_ssl,
|
8
8
|
:location_ids => [], :organization_ids => []
|
9
9
|
end
|
10
10
|
end
|
@@ -1,38 +1,13 @@
|
|
1
1
|
class ImageSearchController < ::ApplicationController
|
2
|
-
def auto_complete_repository_name
|
3
|
-
catch_network_errors do
|
4
|
-
available = image_search_service.available?(params[:search])
|
5
|
-
render :text => available.to_s
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
def auto_complete_image_tag
|
10
|
-
catch_network_errors do
|
11
|
-
tags = image_search_service.search({
|
12
|
-
term: params[:search],
|
13
|
-
tags: 'true'
|
14
|
-
})
|
15
|
-
|
16
|
-
respond_to do |format|
|
17
|
-
format.js do
|
18
|
-
render :json => prepare_for_autocomplete(tags)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
2
|
def search_repository
|
25
3
|
catch_network_errors do
|
26
|
-
|
27
|
-
|
28
|
-
tags: 'false'
|
29
|
-
})
|
4
|
+
tags_enabled = params[:tags] || 'false'
|
5
|
+
result = image_search_service.search(term: params[:search], tags: tags_enabled)
|
30
6
|
|
31
7
|
respond_to do |format|
|
32
|
-
format.js
|
33
|
-
|
34
|
-
|
35
|
-
:use_hub => use_hub? }
|
8
|
+
format.js { render json: prepare_for_autocomplete(result) }
|
9
|
+
format.html do
|
10
|
+
render partial: 'repository_search_results', locals: { repositories: result }
|
36
11
|
end
|
37
12
|
end
|
38
13
|
end
|
@@ -51,13 +26,9 @@ class ImageSearchController < ::ApplicationController
|
|
51
26
|
:status => 500
|
52
27
|
end
|
53
28
|
|
54
|
-
def use_hub?
|
55
|
-
@registry.nil?
|
56
|
-
end
|
57
|
-
|
58
29
|
def action_permission
|
59
30
|
case params[:action]
|
60
|
-
when '
|
31
|
+
when 'search_repository'
|
61
32
|
:search_repository
|
62
33
|
else
|
63
34
|
super
|
data/app/models/container.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class DockerContainerWizardState <
|
1
|
+
class DockerContainerWizardState < ApplicationRecord
|
2
2
|
has_one :preliminary, :class_name => DockerContainerWizardStates::Preliminary,
|
3
3
|
:dependent => :destroy, :validate => true, :autosave => true
|
4
4
|
has_one :image, :class_name => DockerContainerWizardStates::Image,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module DockerContainerWizardStates
|
2
|
-
class Configuration <
|
2
|
+
class Configuration < ApplicationRecord
|
3
3
|
self.table_name_prefix = 'docker_container_wizard_states_'
|
4
4
|
belongs_to :wizard_state, :class_name => DockerContainerWizardState,
|
5
5
|
:foreign_key => :docker_container_wizard_state_id
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module DockerContainerWizardStates
|
2
|
-
class Image <
|
2
|
+
class Image < ApplicationRecord
|
3
3
|
self.table_name_prefix = 'docker_container_wizard_states_'
|
4
4
|
belongs_to :wizard_state, :class_name => DockerContainerWizardState,
|
5
5
|
:foreign_key => :docker_container_wizard_state_id
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class DockerRegistry <
|
1
|
+
class DockerRegistry < ApplicationRecord
|
2
2
|
include Authorizable
|
3
3
|
include Taxonomix
|
4
4
|
include Encryptable
|
@@ -44,14 +44,18 @@ class DockerRegistry < ActiveRecord::Base
|
|
44
44
|
|
45
45
|
def api
|
46
46
|
@api ||= Service::RegistryApi.new(url: url, user: username,
|
47
|
-
password: password)
|
47
|
+
password: password, verify_ssl: verify_ssl)
|
48
48
|
end
|
49
49
|
|
50
50
|
private
|
51
51
|
|
52
52
|
def attempt_login
|
53
53
|
api.ok?
|
54
|
-
rescue
|
55
|
-
|
54
|
+
rescue Excon::Error::Certificate
|
55
|
+
message = 'Unable to verify certificate.\
|
56
|
+
(Disable "Verify ssl" if you still want to use this Docker Registry)'
|
57
|
+
errors.add(:base, _(message))
|
58
|
+
rescue => error
|
59
|
+
errors.add(:base, _('Unable to log in to this Docker Registry - %s') % error)
|
56
60
|
end
|
57
61
|
end
|
@@ -42,11 +42,13 @@ module ForemanDocker
|
|
42
42
|
::Docker::Image.all({ 'filter' => filter }, docker_connection)
|
43
43
|
end
|
44
44
|
|
45
|
-
def tags_for_local_image(image)
|
46
|
-
image.info['RepoTags'].map do |image_tag|
|
45
|
+
def tags_for_local_image(image, tag = nil)
|
46
|
+
result = image.info['RepoTags'].map do |image_tag|
|
47
47
|
_, tag = image_tag.split(':')
|
48
48
|
tag
|
49
49
|
end
|
50
|
+
result = filter_tags(result, tag) if tag
|
51
|
+
result
|
50
52
|
end
|
51
53
|
|
52
54
|
def exist?(name)
|
@@ -127,6 +129,12 @@ module ForemanDocker
|
|
127
129
|
|
128
130
|
protected
|
129
131
|
|
132
|
+
def filter_tags(result, query)
|
133
|
+
result.select do |tag_name|
|
134
|
+
tag_name['name'] =~ /^#{query}/
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
130
138
|
def docker_command
|
131
139
|
yield
|
132
140
|
rescue Excon::Errors::Error, ::Docker::Error::DockerError => e
|