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.
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