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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -1
  3. data/app/assets/javascripts/foreman_docker/container_image_search.js +203 -0
  4. data/app/assets/javascripts/foreman_docker/create_registry.js +15 -0
  5. data/app/assets/stylesheets/foreman_docker/autocomplete.css.scss +4 -0
  6. data/app/controllers/concerns/foreman/controller/parameters/docker_registry.rb +1 -1
  7. data/app/controllers/image_search_controller.rb +6 -35
  8. data/app/helpers/container_steps_helper.rb +0 -1
  9. data/app/models/container.rb +1 -1
  10. data/app/models/docker_container_wizard_state.rb +1 -1
  11. data/app/models/docker_container_wizard_states/configuration.rb +1 -1
  12. data/app/models/docker_container_wizard_states/environment.rb +1 -1
  13. data/app/models/docker_container_wizard_states/image.rb +1 -1
  14. data/app/models/docker_container_wizard_states/preliminary.rb +1 -1
  15. data/app/models/docker_parameter.rb +1 -1
  16. data/app/models/docker_registry.rb +8 -4
  17. data/app/models/foreman_docker/docker.rb +10 -2
  18. data/app/models/service/registry_api.rb +10 -5
  19. data/app/views/containers/steps/_form_buttons.html.erb +5 -7
  20. data/app/views/containers/steps/_image_hub_tab.html.erb +16 -17
  21. data/app/views/containers/steps/image.html.erb +1 -3
  22. data/app/views/image_search/_repository_search_results.html.erb +1 -1
  23. data/app/views/registries/_form.html.erb +5 -3
  24. data/app/views/registries/index.html.erb +11 -10
  25. data/db/migrate/20170508130316_add_verify_ssl_option_to_docker_registries.rb +5 -0
  26. data/lib/foreman_docker/engine.rb +7 -6
  27. data/lib/foreman_docker/version.rb +1 -1
  28. data/test/functionals/image_search_controller_test.rb +120 -169
  29. data/test/integration/container_steps_test.rb +118 -18
  30. data/test/integration/registry_creation_test.rb +19 -0
  31. data/test/units/docker_container_wizard_states/image_test.rb +0 -1
  32. data/test/units/docker_registry_test.rb +0 -8
  33. data/test/units/registry_api_test.rb +13 -1
  34. metadata +10 -6
  35. 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: ebb80b781a7f84fa7654be95ec8d3404a4164931
4
- data.tar.gz: 27d789e92be3bd4b2e10078808f8769aece0874c
3
+ metadata.gz: c16b711b658dfe65317085cac2406cb5797cb7eb
4
+ data.tar.gz: d895b63f9b5a822e9e9124a2ddc07acac9008860
5
5
  SHA512:
6
- metadata.gz: 5a9d5ffaf42b7a30a2a67f41ee5250dba09da5f18ba7e92e02ffe120791de1c5558b7b1b458c2f9e50a48e778dc0efd4e5d6972225345787ea3e8a88c2afb553
7
- data.tar.gz: bd2b7877e5fd9e3c2c95a8991b0c9f189b67cc8d82def3d2e43442f0afb47ffd443664a6a991fa35a3b5fec3fc2cc58ac1a6bfad8272dfd4c7877c9f04342f52
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">&nbsp;' +
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
+ })
@@ -1,3 +1,7 @@
1
1
  #image-confirmation {
2
2
  margin-left: 10px;
3
3
  }
4
+ .small-gutter {
5
+ padding-right: 10px;
6
+ padding-left: 10px;
7
+ }
@@ -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
- repositories = image_search_service.search({
27
- term: params[:search].split(':').first,
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 do
33
- render :partial => 'repository_search_results',
34
- :locals => { :repositories => repositories,
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 'auto_complete_repository_name', 'auto_complete_image_tag', 'search_repository'
31
+ when 'search_repository'
61
32
  :search_repository
62
33
  else
63
34
  super
@@ -42,7 +42,6 @@ module ContainerStepsHelper
42
42
  name.split.last
43
43
  end
44
44
 
45
-
46
45
  def model_for(registry_type)
47
46
  if active_tab.to_s == registry_type.to_s
48
47
  @docker_container_wizard_states_image
@@ -1,4 +1,4 @@
1
- class Container < ActiveRecord::Base
1
+ class Container < ApplicationRecord
2
2
  include Authorizable
3
3
  include Taxonomix
4
4
 
@@ -1,4 +1,4 @@
1
- class DockerContainerWizardState < ActiveRecord::Base
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 < ActiveRecord::Base
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 Environment < ActiveRecord::Base
2
+ class Environment < ApplicationRecord
3
3
  self.table_name_prefix = 'docker_container_wizard_states_'
4
4
  belongs_to :wizard_state, :class_name => DockerContainerWizardState
5
5
 
@@ -1,5 +1,5 @@
1
1
  module DockerContainerWizardStates
2
- class Image < ActiveRecord::Base
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,5 +1,5 @@
1
1
  module DockerContainerWizardStates
2
- class Preliminary < ActiveRecord::Base
2
+ class Preliminary < ApplicationRecord
3
3
  include Taxonomix
4
4
 
5
5
  self.table_name_prefix = 'docker_container_wizard_states_'
@@ -1,4 +1,4 @@
1
- class DockerParameter < ActiveRecord::Base
1
+ class DockerParameter < ApplicationRecord
2
2
  extend FriendlyId
3
3
  friendly_id :key
4
4
  include Parameterizable::ByIdName
@@ -1,4 +1,4 @@
1
- class DockerRegistry < ActiveRecord::Base
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 => e
55
- errors.add(:base, _('Unable to log in to this Docker Registry - %s') % e)
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