foreman_docker 3.1.0 → 3.2.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 +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
|