├── .gitignore ├── .hound.yml ├── .rubocop.yml ├── .rubocop_todo.yml ├── .tx └── config ├── CHANGELOG ├── Contributors ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── javascripts │ │ └── foreman_docker │ │ │ ├── container_image_search.js │ │ │ └── create_registry.js │ └── stylesheets │ │ └── foreman_docker │ │ ├── autocomplete.css.scss │ │ └── terminal.css.scss ├── controllers │ ├── api │ │ └── v2 │ │ │ ├── containers_controller.rb │ │ │ └── registries_controller.rb │ ├── concerns │ │ ├── foreman │ │ │ └── controller │ │ │ │ └── parameters │ │ │ │ └── docker_registry.rb │ │ └── foreman_docker │ │ │ └── find_container.rb │ ├── containers │ │ └── steps_controller.rb │ ├── containers_controller.rb │ ├── image_search_controller.rb │ └── registries_controller.rb ├── helpers │ ├── container_steps_helper.rb │ └── containers_helper.rb ├── models │ ├── concerns │ │ ├── fog_extensions │ │ │ └── fogdocker │ │ │ │ ├── image.rb │ │ │ │ ├── images.rb │ │ │ │ └── server.rb │ │ └── foreman_docker │ │ │ └── parameter_validators.rb │ ├── container.rb │ ├── docker_container_wizard_state.rb │ ├── docker_container_wizard_states │ │ ├── configuration.rb │ │ ├── dns.rb │ │ ├── environment.rb │ │ ├── environment_variable.rb │ │ ├── exposed_port.rb │ │ ├── image.rb │ │ └── preliminary.rb │ ├── docker_parameter.rb │ ├── docker_registry.rb │ ├── environment_variable.rb │ ├── exposed_port.rb │ ├── foreman_docker │ │ ├── compute_resource_extensions.rb │ │ ├── dns.rb │ │ ├── docker.rb │ │ └── taxonomy_extensions.rb │ └── service │ │ ├── containers.rb │ │ └── registry_api.rb ├── overrides │ ├── remove_docker_from_compute_profiles.rb │ └── rename_virtual_machines_containers.rb ├── services │ └── foreman_docker │ │ ├── container_remover.rb │ │ ├── image_search.rb │ │ └── utility.rb └── views │ ├── api │ ├── v1 │ │ └── compute_resources │ │ │ └── docker.json │ └── v2 │ │ ├── compute_resources │ │ └── docker.json │ │ ├── containers │ │ ├── base.json.rabl │ │ ├── index.json.rabl │ │ ├── main.json.rabl │ │ └── show.json.rabl │ │ └── registries │ │ ├── base.json.rabl │ │ ├── index.json.rabl │ │ ├── main.json.rabl │ │ └── show.json.rabl │ ├── compute_resources │ ├── form │ │ └── _docker.html.erb │ └── show │ │ └── _docker.html.erb │ ├── compute_resources_vms │ ├── form │ │ └── _docker.html.erb │ ├── index │ │ └── _docker.html.erb │ └── show │ │ └── _docker.html.erb │ ├── containers │ ├── _list.html.erb │ ├── index.html.erb │ ├── show.html.erb │ └── steps │ │ ├── _form_buttons.html.erb │ │ ├── _image_hub_tab.html.erb │ │ ├── _title.html.erb │ │ ├── configuration.html.erb │ │ ├── environment.html.erb │ │ ├── image.html.erb │ │ └── preliminary.html.erb │ ├── foreman_docker │ └── common_parameters │ │ ├── _dns.erb │ │ ├── _dns_entry.html.erb │ │ ├── _environment_variable.html.erb │ │ ├── _environment_variables.html.erb │ │ ├── _exposed_port.html.erb │ │ └── _exposed_ports.erb │ ├── image_search │ └── _repository_search_results.html.erb │ ├── images │ └── form │ │ └── _docker.html.erb │ └── registries │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── welcome.html.erb ├── config └── routes.rb ├── db └── migrate │ ├── .rubocop.yml │ ├── 20140930175337_add_email_to_compute_resource.rb │ ├── 20141005233312_create_containers.rb │ ├── 20141007225130_add_compute_resource_id_to_container.rb │ ├── 20141009001613_add_tag_to_container.rb │ ├── 20141009011026_add_attributes_to_container.rb │ ├── 20141010173220_create_docker_images.rb │ ├── 20141018110810_add_uuid_to_containers.rb │ ├── 20141024163003_create_docker_registries.rb │ ├── 20141028164206_change_memory_in_container.rb │ ├── 20141028164633_change_cpuset_in_container.rb │ ├── 20141120123003_add_user_credentials_to_docker_registries.rb │ ├── 20141209182008_remove_docker_tables.rb │ ├── 20141222113313_create_wizard_states.rb │ ├── 20150122011747_add_katello_flag_to_docker_wizard_image.rb │ ├── 20150129054944_add_katello_flag_to_containers.rb │ ├── 20150303175646_remove_katello_flag_from_containers.rb │ ├── 20150814205620_change_container_column_type.rb │ ├── 20160605133025_create_docker_parameters.rb │ ├── 20160605134652_move_parameters_to_docker_parameters.rb │ ├── 20170508130316_add_verify_ssl_option_to_docker_registries.rb │ └── 20180105090316_remove_docker_search_permission.rb ├── foreman_docker.gemspec ├── lib ├── foreman_docker.rb ├── foreman_docker │ ├── engine.rb │ └── version.rb └── tasks │ ├── cleanup.rake │ └── test.rake ├── locale ├── Makefile ├── de │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── es │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── foreman_docker.pot ├── fr │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── it │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── ja │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── ko │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── pt_BR │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── ru │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po ├── zanata.xml ├── zh_CN │ ├── LC_MESSAGES │ │ └── foreman_docker.mo │ └── foreman_docker.po └── zh_TW │ ├── LC_MESSAGES │ └── foreman_docker.mo │ └── foreman_docker.po ├── release ├── RELEASE.md ├── changelog └── rollout-release └── test ├── factories ├── compute_resources.rb ├── containers.rb └── docker_registry.rb ├── functionals ├── api │ └── v2 │ │ ├── containers_controller_test.rb │ │ └── registries_controller_test.rb ├── containers_controller_test.rb ├── containers_steps_controller_test.rb └── image_search_controller_test.rb ├── integration ├── container_steps_test.rb ├── container_test.rb └── registry_creation_test.rb ├── test_plugin_helper.rb └── units ├── container_remover_test.rb ├── container_test.rb ├── containers_service_test.rb ├── docker_container_wizard_states └── image_test.rb ├── docker_registry_test.rb ├── foreman_docker ├── compute_resource_extensions_test.rb └── docker_test.rb ├── image_search_service_test.rb ├── registry_api_test.rb └── utility_service_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | Gemfile.lock 3 | log/*.log 4 | pkg/ 5 | .idea/ 6 | test/dummy/db/*.sqlite3 7 | test/dummy/db/*.sqlite3-journal 8 | test/dummy/log/*.log 9 | test/dummy/tmp/ 10 | test/dummy/.sass-cache 11 | .ruby-version 12 | .ruby-gemset 13 | public/assets/ 14 | locale/*.mo 15 | locale/*/*.pox 16 | locale/*/*.edit.po 17 | locale/*/*.po.time_stamp 18 | 19 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # TODO: remove this file by either moving cops here or fixing code 2 | inherit_from: 3 | - .rubocop_todo.yml 4 | 5 | AllCops: 6 | TargetRubyVersion: 2.2 7 | TargetRailsVersion: 5.1 8 | 9 | Rails: # always run the rails cops 10 | Enabled: true 11 | 12 | # Don't is_a? over kind_of? 13 | Style/ClassCheck: 14 | Enabled: false 15 | 16 | # Don't enforce documentation 17 | Style/Documentation: 18 | Enabled: false 19 | 20 | # Support both ruby19 and hash_rockets 21 | Style/HashSyntax: 22 | Enabled: false 23 | 24 | Style/StringLiterals: 25 | Enabled: false 26 | 27 | Style/FrozenStringLiteralComment: 28 | Enabled: false 29 | 30 | Metrics/ClassLength: 31 | Exclude: 32 | - 'test/**/*' 33 | 34 | Performance/FixedSize: 35 | Exclude: 36 | - 'test/**/*' 37 | 38 | Rails/Date: 39 | Exclude: 40 | - foreman_docker.gemspec 41 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [foreman.docker] 5 | file_filter = locale//foreman_docker.edit.po 6 | source_file = locale/foreman_docker.pot 7 | source_lang = en 8 | type = PO 9 | 10 | -------------------------------------------------------------------------------- /Contributors: -------------------------------------------------------------------------------- 1 | Contributors (sorted alphabetically) 2 | 3 | Adam Ruzicka 4 | Amos Benari 5 | Bryan Kearney 6 | Daniel Lobato 7 | Daniel Lobato Garcia 8 | Daniel Lobato García 9 | Daniel Lobato García 10 | David Davis 11 | Dmitri Dolguikh 12 | Dominic Cleal 13 | Dominic Cleal 14 | Eric D. Helms 15 | John Mitsch 16 | June Zhang 17 | Marek Hulan 18 | Marek Hulán 19 | Martin Loy 20 | Michael Moll 21 | Mike McCune 22 | Ohad Levy 23 | Ondřej Pražák 24 | Ori Rabin 25 | Partha Aji 26 | Ranjan Kumar 27 | Sebastian Gräßl 28 | Sebastian Gräßl 29 | Shlomi Zadok 30 | Stephen Benjamin 31 | Swapnil Abnave 32 | Tim Meusel 33 | Tom Caspy 34 | Vanya Jauhal 35 | Walden Raines 36 | abenari 37 | orrabin 38 | oshtaier 39 | pujan14 40 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Declare your gem's dependencies in foreman_docker.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Foreman Docker Plugin 2 | 3 | **This plugin has been discontinued. The latest release only helps with its removal. If you're interested in taking over the maintanance, let us know.** 4 | 5 | In order to remove the plugin from you Foreman installation, following steps need to be taken 6 | 7 | 1. make a backup 8 | 1. upgrade to last version of foreman-docker 5.0 (and Katello if you're using it) 9 | 1. run `foreman-rake db:migrate` (if you have Katello, this will erase docker data) 10 | 1. run `foreman-rake foreman_docker:cleanup` (cleans up all data that this plugin introduced) 11 | 1. `yum remove tfm-rubygem-foreman_docker` or `apt remove ruby-foreman-docker` 12 | 1. `yum remove tfm-rubygem-hammer_cli_foreman_docker` or `apt remove ruby-hammer-cli-foreman-docker` unless you still use katello commands from it 13 | 1. update apipie cache `foreman-rake apipie:cache` 14 | 1. `service httpd restart` 15 | 16 | 17 | [![Code Climate](https://codeclimate.com/github/theforeman/foreman-docker/badges/gpa.svg)](https://codeclimate.com/github/theforeman/foreman-docker) 18 | [![Gem Version](https://badge.fury.io/rb/foreman_docker.svg)](http://badge.fury.io/rb/foreman_docker) 19 | [![Dependency Status](https://gemnasium.com/theforeman/foreman-docker.svg)](https://gemnasium.com/theforeman/foreman-docker) 20 | [![Issue Stats](http://issuestats.com/github/theforeman/foreman-docker/badge/pr)](http://issuestats.com/github/theforeman/foreman-docker) 21 | 22 | ```foreman_docker``` enables provisioning and managing of [Docker](http://docker.com) containers and images in [Foreman](http://github.com/theforeman/foreman), all of that under the GPL v3+ license. 23 | 24 | * Website: [TheForeman.org](http://theforeman.org) 25 | * ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman) 26 | * Issues: [foreman_docker Redmine](http://projects.theforeman.org/projects/docker/issues) 27 | * Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About) 28 | * Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net) 29 | * Mailing lists: 30 | * [foreman-users](https://groups.google.com/forum/?fromgroups#!forum/foreman-users) 31 | * [foreman-dev](https://groups.google.com/forum/?fromgroups#!forum/foreman-dev) 32 | 33 | ## Features 34 | 35 | * Special view with logs and processes of Foreman managed containers 36 | ![](http://i.imgur.com/D21bdgj.png) 37 | ![](http://i.imgur.com/XnrPTZC.png) 38 | * Wizard for container creation and cgroups configuration 39 | ![Select a docker image](http://i.imgur.com/IoMuNnr.png) 40 | ![Cgroups configuration](http://i.imgur.com/74d99Tf.png) 41 | * Commit and upload containers: creates an image with the status of your current container 42 | ![Commit and upload to the docker hub](http://i.imgur.com/coF5Y0L.png) 43 | * Container listing and basic CRUD operations 44 | ![](http://i.imgur.com/DPcaHkZ.png) 45 | 46 | ### Planned 47 | * [Kubernetes](https://github.com/kubernetes/kubernetes/) integration 48 | * Events stream ([#8037](http://projects.theforeman.org/issues/8037)) 49 | * Tight integration between Docker hosts [Atomic](http://www.projectatomic.io/) and [CoreOS](http://coreos.com/) and containers ([#7653](http://projects.theforeman.org/issues/7653), [#7652](http://projects.theforeman.org/issues/7652)) 50 | * Quickstart images - pre-supplied images and configuration ([#7869](http://projects.theforeman.org/issues/7869)) 51 | * Links to other containers ([#7866](http://projects.theforeman.org/issues/7866)) 52 | * API ([#7874](http://projects.theforeman.org/issues/7874)) 53 | * [Hammer CLI](http://github.com/theforeman/hammer-cli-foreman) support ([#8227](http://projects.theforeman.org/issues/8227)) 54 | 55 | ## Installation 56 | 57 | Please see the Foreman manual for appropriate instructions: 58 | 59 | * [Foreman: How to Install a Plugin](http://theforeman.org/manuals/latest/index.html#6.1InstallaPlugin) 60 | 61 | ### Red Hat, CentOS, Fedora, Scientific Linux (rpm) 62 | 63 | Set up the repo as explained in the link above, then run 64 | 65 | # yum install ruby193-rubygem-foreman_docker 66 | 67 | ### Debian, Ubuntu (deb) 68 | 69 | Set up the repo as explained in the link above, then run 70 | 71 | # apt-get install ruby-foreman-docker 72 | 73 | ### Bundle (gem) 74 | 75 | Add the following to bundler.d/Gemfile.local.rb in your Foreman installation directory (/usr/share/foreman by default) 76 | 77 | $ gem 'foreman_docker' 78 | 79 | Then run `bundle install` and `foreman-rake db:migrate` from the same directory 80 | 81 | -------------- 82 | 83 | To verify that the installation was successful, go to Foreman, top bar **Administer > About** and check 'foreman_docker' shows up in the **System Status** menu under the Plugins tab. You should also see a **'Containers'** button show up in the top bar, similar to this 84 | 85 | ![](http://i.imgur.com/Ug14Ktl.png) 86 | 87 | ## Configuration 88 | 89 | Go to **Infrastructure > Compute Resources** and click on "New Compute Resource". 90 | 91 | Choose the **Docker provider**, and fill in all the fields. User name, password, and email are used so that Docker clients such as Foreman can make the host download images from the Docker hub. Your password will be encrypted in the database. 92 | 93 | That's it. You're now ready to create and manage containers in your new Docker compute resource. 94 | 95 | ## Compatibility 96 | 97 | | Foreman | Plugin | 98 | | ---------------:| --------------:| 99 | | >= 1.5 | 0.0.1 - 0.0.3 | 100 | | >= 1.6 | 0.1.0 - 0.2.0 | 101 | | >= 1.7 | 1.0.0 - 2.1.1 | 102 | | >= 1.7 | 3.0.0+ | 103 | | >= 1.15 | 3.1.0+ | 104 | 105 | ## Docker Registry API Compatibility 106 | 107 | | Plugin version | Registry API version | 108 | | ---------------:| --------------:| 109 | | < 3.1.0 | [v1](http://docs.master.dockerproject.org/v1.7/reference/api/registry_api/) | 110 | | >= 3.1.0 | [v1](http://docs.master.dockerproject.org/v1.7/reference/api/registry_api/), [v2](https://docs.docker.com/registry/spec/api/)***** | 111 | 112 | _*** Note:** API v2 as default and v1 to fall back on._ 113 | 114 | See extras/RELEASE.md for more detailed information on compatibility and releases. 115 | 116 | ## How to contribute? 117 | 118 | Generally, follow the [Foreman guidelines](http://theforeman.org/contribute.html). For code-related contributions, fork this project and send a pull request with all changes. Some things to keep in mind: 119 | * Code from the master branch can contain features only present in [Fog's](http://github.com/fog/fog) master branch, we commit to wait for the next Fog release to put that code in a foreman-docker release. 120 | * [Follow the rules](http://theforeman.org/contribute.html#SubmitPatches) about commit message style and create a Redmine issue. Doing this right will help reviewers to get your contribution merged faster. 121 | * [Rubocop](https://github.com/bbatsov/rubocop) will analyze your code, you can run it locally with `rake rubocop`. 122 | * All of our pull requests run the full test suite in our [Jenkins CI system](http://ci.theforeman.org/). Please include tests in your pull requests for any additions or changes in functionality 123 | 124 | 125 | ### Testing 126 | 127 | Run `rake test:docker` from your Foreman directory to run the test suite. 128 | 129 | ## Latest code 130 | 131 | You can get the develop branch of the plugin by specifying your Gemfile in this way: 132 | 133 | gem 'foreman_docker', :git => "https://github.com/theforeman/foreman-docker.git" 134 | 135 | # Copyright 136 | 137 | Copyright (c) 2014 Amos Benari 138 | 139 | This program is free software: you can redistribute it and/or modify 140 | it under the terms of the GNU General Public License as published by 141 | the Free Software Foundation, either version 3 of the License, or 142 | (at your option) any later version. 143 | 144 | This program is distributed in the hope that it will be useful, 145 | but WITHOUT ANY WARRANTY; without even the implied warranty of 146 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 147 | GNU General Public License for more details. 148 | 149 | You should have received a copy of the GNU General Public License 150 | along with this program. If not, see . 151 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | begin 4 | require 'bundler/setup' 5 | rescue LoadError 6 | Rails.logger.error 'You must `gem install bundler` and `bundle install` to run rake tasks' 7 | end 8 | 9 | begin 10 | require 'rubocop/rake_task' 11 | RuboCop::RakeTask.new 12 | rescue => _ 13 | puts "Rubocop not loaded." 14 | end 15 | 16 | task :default do 17 | Rake::Task['rubocop'].execute 18 | end 19 | -------------------------------------------------------------------------------- /app/assets/javascripts/foreman_docker/container_image_search.js: -------------------------------------------------------------------------------- 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('

' + result.responseText + '

', '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('

' + result.responseText + '

', '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 = ' ' + 137 | resultType + ' ' + value + ' is ' 138 | 139 | if (!available) 140 | wrapper += 'not'; 141 | 142 | return wrapper + ' available.'; 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 | }); 204 | -------------------------------------------------------------------------------- /app/assets/javascripts/foreman_docker/create_registry.js: -------------------------------------------------------------------------------- 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 | }) 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/foreman_docker/autocomplete.css.scss: -------------------------------------------------------------------------------- 1 | #image-confirmation { 2 | margin-left: 10px; 3 | } 4 | .small-gutter { 5 | padding-right: 10px; 6 | padding-left: 10px; 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/foreman_docker/terminal.css.scss: -------------------------------------------------------------------------------- 1 | .terminal { 2 | .terminal-output .format { 3 | display: inline-block; 4 | } 5 | .terminal-output div div { 6 | display: inline-block; 7 | } 8 | .clipboard { 9 | position: absolute; 10 | bottom: 0; 11 | left: 0; 12 | opacity: 0.01; 13 | filter: alpha(opacity = 0.01); 14 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0.01); 15 | width: 2px; 16 | } 17 | } 18 | 19 | .terminal { 20 | padding: 10px; 21 | position: relative; 22 | overflow-y: scroll ; 23 | } 24 | 25 | .terminal { 26 | .terminal-output div div { 27 | display: block; 28 | line-height: 14px; 29 | height: auto; 30 | } 31 | .prompt { 32 | display: block; 33 | line-height: 14px; 34 | height: auto; 35 | float: left; 36 | } 37 | font-family: FreeMono,monospace; 38 | color: rgba(255, 255, 255, 1); 39 | background-color: rgba(47, 47, 47, 1); 40 | font-size: 12px; 41 | line-height: 14px; 42 | } 43 | 44 | .terminal-output > div { 45 | min-height: 14px; 46 | } 47 | 48 | .terminal { 49 | .terminal-output div span { 50 | display: inline-block; 51 | } 52 | .terminal-output div { 53 | div::-moz-selection, span::-moz-selection { 54 | color: rgba(255, 255, 255, 1); 55 | background-color: rgba(47, 47, 47, 1); 56 | } 57 | div { 58 | a::-moz-selection, &::selection, a::selection { 59 | color: rgba(255, 255, 255, 1); 60 | background-color: rgba(47, 47, 47, 1); 61 | } 62 | } 63 | span::selection { 64 | color: rgba(255, 255, 255, 1); 65 | background-color: rgba(47, 47, 47, 1); 66 | } 67 | } 68 | .terminal-output div.error { 69 | color: red; 70 | div { 71 | color: red; 72 | } 73 | } 74 | } 75 | 76 | .clear { 77 | clear: both; 78 | } 79 | 80 | .terminal a { 81 | color: #0F60FF; 82 | &:hover { 83 | color: red; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/controllers/api/v2/registries_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V2 3 | class RegistriesController < ::Api::V2::BaseController 4 | include Foreman::Controller::Parameters::DockerRegistry 5 | before_action :find_resource, :except => %w(index create) 6 | 7 | resource_description do 8 | resource_id 'registries' 9 | api_version 'v2' 10 | api_base_url '/docker/api/v2' 11 | end 12 | 13 | def_param_group :registry do 14 | param :registry, Hash, :required => true, :action_aware => true do 15 | param :name, String, :required => true 16 | param_group :taxonomies, ::Api::V2::BaseController 17 | param :url, String, :required => true 18 | param :description, String 19 | param :username, String 20 | param :password, String 21 | end 22 | end 23 | 24 | api :GET, '/registries/', N_('List all docker registries') 25 | param_group :search_and_pagination, ::Api::V2::BaseController 26 | def index 27 | @registries = DockerRegistry.search_for(params[:search], :order => params[:order]) 28 | .paginate(:page => params[:page]) 29 | end 30 | 31 | api :GET, '/registries/:id', N_("Show a docker registry") 32 | param :id, :identifier, :required => true 33 | def show 34 | end 35 | 36 | api :POST, '/registries/', N_('Create a docker registry') 37 | param_group :registry, :as => :create 38 | def create 39 | @registry = DockerRegistry.new(docker_registry_params) 40 | process_response @registry.save 41 | end 42 | 43 | api :PUT, '/registries/:id', N_('Update a docker registry') 44 | param :id, :identifier, :required => true 45 | param_group :registry, :as => :update 46 | def update 47 | process_response @registry.update_attributes(docker_registry_params) 48 | end 49 | 50 | api :DELETE, '/registries/:id/', N_('Delete a docker registry') 51 | param :id, :identifier, :required => true 52 | def destroy 53 | process_response @registry.destroy 54 | end 55 | 56 | private 57 | 58 | def resource_class 59 | DockerRegistry 60 | end 61 | 62 | def docker_registry_url(registry) 63 | registry_url(registry) 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /app/controllers/concerns/foreman/controller/parameters/docker_registry.rb: -------------------------------------------------------------------------------- 1 | module Foreman::Controller::Parameters::DockerRegistry 2 | extend ActiveSupport::Concern 3 | 4 | class_methods do 5 | def docker_registry_params_filter 6 | Foreman::ParameterFilter.new(::DockerRegistry).tap do |filter| 7 | filter.permit :name, :url, :username, :password, :description, :verify_ssl, 8 | :location_ids => [], :organization_ids => [] 9 | end 10 | end 11 | end 12 | 13 | def docker_registry_params 14 | param_name = parameter_filter_context.api? ? 'registry' : 'docker_registry' 15 | self.class.docker_registry_params_filter.filter_params(params, parameter_filter_context, 16 | param_name 17 | ) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/concerns/foreman_docker/find_container.rb: -------------------------------------------------------------------------------- 1 | # To be replaced by find_resource, FindCommon after 1.6 support is deprecated 2 | module ForemanDocker 3 | module FindContainer 4 | extend ActiveSupport::Concern 5 | 6 | def find_container 7 | if params[:id].blank? 8 | not_found 9 | return 10 | end 11 | @container = Container.authorized("#{action_permission}_#{controller_name}".to_sym) 12 | .find(params[:id]) 13 | end 14 | 15 | def allowed_resources 16 | ForemanDocker::Docker.authorized(:view_compute_resources) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/containers/steps_controller.rb: -------------------------------------------------------------------------------- 1 | module Containers 2 | class StepsController < ::ApplicationController 3 | include Wicked::Wizard 4 | include ForemanDocker::FindContainer 5 | 6 | steps :preliminary, :image, :configuration, :environment 7 | 8 | before_action :find_state 9 | before_action :build_state, :only => [:update] 10 | before_action :set_form, :only => [:show] 11 | 12 | def show 13 | @container_resources = allowed_resources if step == :preliminary 14 | render_wizard 15 | end 16 | 17 | def update 18 | if step == wizard_steps.last 19 | if process_resource!(@state).nil? 20 | render_wizard @state 21 | else 22 | params[:start_on_create] ? create_container : create_container(false) 23 | end 24 | else 25 | render_wizard @state 26 | end 27 | end 28 | 29 | private 30 | 31 | def find_state 32 | @state = DockerContainerWizardState.find(params[:wizard_state_id]) 33 | rescue ActiveRecord::RecordNotFound 34 | not_found 35 | end 36 | 37 | def build_state 38 | s = @state.send(:"build_#{step}", state_params) 39 | instance_variable_set("@docker_container_wizard_states_#{step}", s) 40 | end 41 | 42 | def docker_parameters_permited_params 43 | [:key, :reference_id, :value, :_destroy, :id] 44 | end 45 | 46 | def state_params 47 | attrs = case step 48 | when :preliminary 49 | [:wizard_state, :compute_resource_id] 50 | when :image 51 | [:repository_name, :tag, :wizard_state, :registry_id, :capsule_id, :katello] 52 | when :configuration 53 | [:name, :command, :entrypoint, :cpu_set, :cpu_shares, :memory, :wizard_state] 54 | when :environment 55 | [:tty, :docker_container_wizard_state_id, 56 | :attach_stdin, :attach_stdout, :attach_stderr, 57 | :exposed_ports_attributes => docker_parameters_permited_params, 58 | :environment_variables_attributes => docker_parameters_permited_params, 59 | :dns_attributes => docker_parameters_permited_params 60 | ] 61 | end 62 | 63 | params.require("docker_container_wizard_states_#{step}").permit(*attrs) 64 | end 65 | 66 | def set_form 67 | instance_variable_set( 68 | "@docker_container_wizard_states_#{step}", 69 | @state.send(:"#{step}") || @state.send(:"build_#{step}")) 70 | end 71 | 72 | def create_container(start = true) 73 | @state.send(:"create_#{step}", state_params) 74 | service = Service::Containers.new 75 | container = if start.is_a? TrueClass 76 | service.start_container!(@state) 77 | else 78 | service.create_container!(@state) 79 | end 80 | 81 | if container.present? 82 | process_success(:object => container, :success_redirect => container_path(container)) 83 | else 84 | @docker_container_wizard_states_environment = @state.environment 85 | process_error( 86 | :redirect => containers_path, 87 | :error_msg => service.full_messages.join(','), 88 | :object => @state.environment) 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /app/controllers/containers_controller.rb: -------------------------------------------------------------------------------- 1 | class ContainersController < ::ApplicationController 2 | include ForemanDocker::FindContainer 3 | 4 | before_action :find_container, :only => [:show, :commit, :power] 5 | 6 | def index 7 | @container_resources = allowed_resources 8 | if @container_resources.empty? 9 | warning('You need a Compute Resource of type Docker to start managing containers') 10 | redirect_to new_compute_resource_path 11 | end 12 | # This should only rescue Fog::Errors, but Fog returns all kinds of errors... 13 | rescue 14 | process_error 15 | end 16 | 17 | def new 18 | redirect_to wizard_state_step_path(:wizard_state_id => DockerContainerWizardState.create.id, 19 | :id => :preliminary) 20 | end 21 | 22 | def destroy 23 | if (deleted_identifier = container_deletion) 24 | process_success(:success_redirect => containers_path, 25 | :success_msg => (_("Container %s is being deleted.") % 26 | deleted_identifier)) 27 | else 28 | error(_('Your container could not be deleted in Docker')) 29 | if @container.present? 30 | process_error(:redirect => containers_path) 31 | else 32 | redirect_back(:fallback_location => containers_path) 33 | end 34 | end 35 | rescue ActiveRecord::RecordNotFound 36 | not_found 37 | end 38 | 39 | def show 40 | end 41 | 42 | def commit 43 | ForemanDocker::Docker.get_container(@container).commit(:author => params[:commit][:author], 44 | :repo => params[:commit][:repo], 45 | :tag => params[:commit][:tag], 46 | :comment => params[:commit][:comment]) 47 | 48 | process_success :success_redirect => :back, 49 | :success_msg => _("%{container} commit was successful") % 50 | { :container => @container } 51 | rescue => e 52 | process_error :redirect => :back, :error_msg => _("Failed to commit %{container}: %{e}") % 53 | { :container => @container, :e => e } 54 | end 55 | 56 | def power 57 | compute_resource = @container.compute_resource 58 | @docker_container = compute_resource.find_vm_by_uuid(@container.uuid) 59 | run_container_action(@docker_container.ready? ? :stop : :start) 60 | end 61 | 62 | def run_container_action(action) 63 | if @docker_container.send(action) 64 | @docker_container.reload 65 | notice _("%{vm} is now %{vm_state}") % 66 | { :vm => @docker_container, :vm_state => @docker_container.state.capitalize } 67 | redirect_to containers_path(:id => @container.id) 68 | else 69 | error _("failed to %{action} %{vm}") % { :action => _(action), :vm => @docker_container } 70 | redirect_back(:fallback_location => containers_path) 71 | end 72 | # This should only rescue Fog::Errors, but Fog returns all kinds of errors... 73 | rescue => e 74 | error _("Error - %{message}") % { :message => _(e.message) } 75 | redirect_back(:fallback_location => containers_path) 76 | end 77 | 78 | private 79 | 80 | def action_permission 81 | case params[:action] 82 | when 'auto_complete_repository_name', 'auto_complete_tag', 'search_repository' 83 | :view 84 | when 'commit' 85 | :commit 86 | when 'power' 87 | :power_compute_resources_vms 88 | else 89 | super 90 | end 91 | end 92 | 93 | def current_permission 94 | if params[:action] == 'power' 95 | :power_compute_resources_vms 96 | else 97 | super 98 | end 99 | end 100 | 101 | def container_deletion 102 | # TODO: Refactor to recognize params[:compute_resource_id] as well. 103 | compute_resource_id = params[:compute_resource_id].present? ? params[:compute_resource_id] : nil 104 | if compute_resource_id 105 | container_uuid = params[:id] 106 | @container ||= Container.authorized("#{action_permission}_#{controller_name}".to_sym).find_by_uuid(container_uuid) 107 | else 108 | find_container 109 | compute_resource_id = @container.compute_resource_id 110 | container_uuid = @container.uuid 111 | end 112 | 113 | deleted_identifier = ForemanDocker::ContainerRemover.remove_unmanaged( 114 | compute_resource_id, container_uuid) 115 | @container.destroy if @container.present? 116 | deleted_identifier 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /app/controllers/image_search_controller.rb: -------------------------------------------------------------------------------- 1 | class ImageSearchController < ::ApplicationController 2 | def search_repository 3 | catch_network_errors do 4 | tags_enabled = params[:tags] || 'false' 5 | result = image_search_service.search(term: params[:search], tags: tags_enabled) 6 | 7 | respond_to do |format| 8 | format.js { render json: prepare_for_autocomplete(result) } 9 | format.html do 10 | render partial: 'repository_search_results', locals: { repositories: result } 11 | end 12 | end 13 | end 14 | end 15 | 16 | private 17 | 18 | def catch_network_errors 19 | yield 20 | rescue Docker::Error::NotFoundError => e 21 | # not an error 22 | logger.debug "image not found: #{e.backtrace}" 23 | render :js, :nothing => true 24 | rescue Docker::Error::DockerError, Excon::Errors::Error, SystemCallError => e 25 | render :js => _("An error occured during repository search: '%s'") % e.message, 26 | :status => 500 27 | end 28 | 29 | def action_permission 30 | case params[:action] 31 | when 'search_repository' 32 | :search_repository 33 | else 34 | super 35 | end 36 | end 37 | 38 | # This is the format jQuery UI autocomplete expects 39 | def prepare_for_autocomplete(tags) 40 | tags.map do |tag| 41 | tag = tag.is_a?(Hash) ? tag.fetch('name', tag) : tag 42 | tag = CGI.escapeHTML(tag) 43 | { :label => tag, :value => tag } 44 | end 45 | end 46 | 47 | def image_search_service 48 | @image_search_service ||= ForemanDocker::ImageSearch.new(*sources) 49 | end 50 | 51 | def sources 52 | if params[:registry] == 'hub' 53 | @registry ||= Service::RegistryApi.docker_hub 54 | @compute_resource ||= ComputeResource.authorized(:view_compute_resources).find(params[:id]) 55 | [@registry, @compute_resource] 56 | elsif params[:registry] == 'registry' && params[:registry_id].present? 57 | @registry ||= DockerRegistry.authorized(:view_registries) 58 | .find(params[:registry_id]).api 59 | [@registry] 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/controllers/registries_controller.rb: -------------------------------------------------------------------------------- 1 | class RegistriesController < ::ApplicationController 2 | include Foreman::Controller::AutoCompleteSearch 3 | include Foreman::Controller::Parameters::DockerRegistry 4 | before_action :find_registry, :only => [:edit, :update, :destroy] 5 | 6 | def index 7 | @registries = DockerRegistry.search_for(params[:search], :order => params[:order]) 8 | .paginate :page => params[:page] 9 | end 10 | 11 | def new 12 | @registry = DockerRegistry.new 13 | end 14 | 15 | def create 16 | @registry = DockerRegistry.new(docker_registry_params) 17 | if @registry.save 18 | process_success 19 | else 20 | process_error 21 | end 22 | end 23 | 24 | def edit 25 | end 26 | 27 | def update 28 | if @registry.update_attributes(docker_registry_params) 29 | process_success 30 | else 31 | process_error 32 | end 33 | end 34 | 35 | def destroy 36 | if @registry.destroy 37 | process_success 38 | else 39 | process_error 40 | end 41 | end 42 | 43 | protected 44 | 45 | def model_of_controller 46 | DockerRegistry 47 | end 48 | 49 | def find_registry 50 | @registry = DockerRegistry.find(params[:id]) 51 | rescue ActiveRecord::RecordNotFound 52 | not_found 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/helpers/container_steps_helper.rb: -------------------------------------------------------------------------------- 1 | module ContainerStepsHelper 2 | def container_wizard(step) 3 | wizard_header( 4 | step, 5 | *humanized_steps 6 | ) 7 | end 8 | 9 | def select_registry(f) 10 | registries = DockerRegistry.with_taxonomy_scope_override(@location, @organization) 11 | .authorized(:view_registries) 12 | field(f, 'docker_container_wizard_states_image[registry_id]', :label => _("Registry")) do 13 | collection_select :docker_container_wizard_states_image, :registry_id, 14 | registries, 15 | :id, :name, 16 | { :prompt => _("Select a registry") }, 17 | :class => "form-control", :disabled => registries.size == 0 18 | end 19 | end 20 | 21 | def humanized_steps 22 | [_('Preliminary'), _('Image'), _('Configuration'), _('Environment')] 23 | end 24 | 25 | def last_step? 26 | step == wizard_steps.last 27 | end 28 | 29 | def taxonomy_icon(taxonomy) 30 | taxonomy == 'locations' ? 'globe' : 'briefcase' 31 | end 32 | 33 | def tab_class(tab_name) 34 | active_tab.to_s == tab_name.to_s ? "active" : "" 35 | end 36 | 37 | # el7 returns -> "name" => "docker.io: docker.io/centos", 38 | # while f20 returns -> "name" => "centos" 39 | # we need repo name to be => "docker.io/centos" for el7 and "centos" for fedora 40 | # to ensure proper search with respect to the tags, image creation etc. 41 | def cleanup_image_name(name) 42 | name.split.last 43 | end 44 | 45 | def model_for(registry_type) 46 | if active_tab.to_s == registry_type.to_s 47 | @docker_container_wizard_states_image 48 | else 49 | DockerContainerWizardStates::Image.new(:wizard_state => @state) 50 | end 51 | end 52 | 53 | def image_search_wrapper_class(model) 54 | if model.errors.messages[:image] 55 | 'form-group has-error' 56 | else 57 | 'form-group' 58 | end 59 | end 60 | 61 | def tab_active?(registry) 62 | active_tab == registry.to_sym 63 | end 64 | 65 | def active_tab 66 | if @docker_container_wizard_states_image.katello? 67 | :katello 68 | elsif @docker_container_wizard_states_image.registry_id.nil? 69 | :hub 70 | else 71 | :registry 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /app/helpers/containers_helper.rb: -------------------------------------------------------------------------------- 1 | module ContainersHelper 2 | def managed?(container, resource) 3 | uuids_in_resource(resource).include? container.identity 4 | end 5 | 6 | def uuids_in_resource(resource) 7 | @uuids_in_resource ||= {} 8 | @uuids_in_resource[resource.id] ||= Container.where(:compute_resource_id => resource.id) 9 | .pluck(:uuid) 10 | end 11 | 12 | def link_to_container(container, resource) 13 | link_to_if_authorized container.name[1..-1].titleize, 14 | container_link_hash(container, resource) 15 | end 16 | 17 | def link_to_taxonomies(taxonomies) 18 | taxonomies.map { |taxonomy| link_to(taxonomy) }.join(" ") 19 | end 20 | 21 | def container_link_hash(container, resource) 22 | if managed?(container, resource) 23 | hash_for_container_path(:id => Container.find_by_uuid(container.identity).id) 24 | else 25 | hash_for_compute_resource_vm_path(:compute_resource_id => resource, 26 | :id => container.identity) 27 | end 28 | end 29 | 30 | def container_title_actions(container) 31 | @compute_resource = container.compute_resource 32 | title_actions( 33 | button_group( 34 | link_to(_('Commit'), '#commit-modal', 35 | :'data-toggle' => 'modal', 36 | :class => 'btn btn-default') 37 | ), 38 | button_group(container_power_action(container.in_fog)), 39 | button_group( 40 | display_delete_if_authorized( 41 | hash_for_container_path(:id => container.id) 42 | .merge(:auth_object => container, 43 | :auth_action => 'destroy', 44 | :authorizer => authorizer), 45 | :confirm => _("Delete %s?") % container.name, 46 | :class => 'btn btn-default') 47 | ) 48 | ) 49 | end 50 | 51 | def container_power_action(vm, authorizer = nil) 52 | if managed?(vm, @compute_resource) 53 | id = Container.find_by_uuid(vm.identity).id 54 | opts = hash_for_power_container_path(:id => id) 55 | .merge(:auth_object => @compute_resource, 56 | :permission => 'power_compute_resources_vms', 57 | :authorizer => authorizer) 58 | else 59 | opts = hash_for_power_compute_resource_vm_path(:compute_resource_id => @compute_resource, 60 | :id => vm.identity) 61 | .merge(:auth_object => @compute_resource, :permission => 'power_compute_resources_vms', 62 | :authorizer => authorizer) 63 | end 64 | html = if vm.ready? 65 | { :confirm => power_on_off_message(vm), :class => "btn btn-danger" } 66 | else 67 | { :class => "btn btn-info" } 68 | end 69 | 70 | display_link_if_authorized "Power #{action_string(vm)}", opts, html.merge(:method => :put) 71 | end 72 | 73 | def power_on_off_message(vm) 74 | _("Are you sure you want to power %{act} %{vm}?") % { :act => action_string(vm).downcase.strip, 75 | :vm => vm } 76 | end 77 | 78 | def processes(container) 79 | ForemanDocker::Docker.get_container(container).top 80 | end 81 | 82 | def logs(container, opts = {}) 83 | ForemanDocker::Docker.get_container(container).logs(opts) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /app/models/concerns/fog_extensions/fogdocker/image.rb: -------------------------------------------------------------------------------- 1 | module FogExtensions 2 | module Fogdocker 3 | module Image 4 | extend ActiveSupport::Concern 5 | 6 | include ActionView::Helpers::NumberHelper 7 | 8 | def name 9 | repo_tags.empty? ? (repository || id) : repo_tags.first 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/concerns/fog_extensions/fogdocker/images.rb: -------------------------------------------------------------------------------- 1 | # Compatibility fixes - to be removed once 1.7 compatibility is no longer required 2 | module FogExtensions 3 | module Fogdocker 4 | module Images 5 | extend ActiveSupport::Concern 6 | 7 | def image_search(query = {}) 8 | Docker::Util.parse_json(Docker.connection.get('/images/search', query)).map do |image| 9 | downcase_hash_keys(image) 10 | end 11 | end 12 | 13 | def downcase_hash_keys(hash, k = []) 14 | if hash.is_a?(Hash) 15 | return hash.reduce({}) { |a, e| a.merge! downcase_hash_keys(e[-1], k + [e[0]]) } 16 | end 17 | { k.join('_').gsub(/([a-z])([A-Z])/, '\1_\2').downcase => hash } 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/concerns/fog_extensions/fogdocker/server.rb: -------------------------------------------------------------------------------- 1 | module FogExtensions 2 | module Fogdocker 3 | module Server 4 | extend ActiveSupport::Concern 5 | 6 | include ActionView::Helpers::NumberHelper 7 | 8 | def state 9 | state_running ? 'Running' : 'Stopped' 10 | end 11 | 12 | # Last time a container was started 13 | # WARNING: this doesn't mean the container has been running since then. 14 | def started_at 15 | attributes['state_started_at'] 16 | end 17 | 18 | def image_friendly_name 19 | attributes['config_image'] 20 | end 21 | 22 | def command 23 | c = [] 24 | c += entrypoint if entrypoint.present? 25 | c += cmd if cmd.present? 26 | c.join(' ') 27 | end 28 | 29 | def poweroff 30 | service.vm_action(:id => id, :action => :kill) 31 | end 32 | 33 | def reset 34 | poweroff 35 | start 36 | end 37 | 38 | def vm_description 39 | _('%{cores} cores and %{memory} memory') % 40 | { :cores => cpus, :memory => number_to_human_size(memory.to_i) } 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/models/concerns/foreman_docker/parameter_validators.rb: -------------------------------------------------------------------------------- 1 | module ForemanDocker 2 | module ParameterValidators 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | validate :validate_unique_parameter_keys 7 | end 8 | 9 | def validate_unique_parameter_keys 10 | parameters_symbol = [:environment_variables, :exposed_ports, :dns] 11 | parameters_symbol.each do |param_symbol| 12 | keys = [] 13 | errors = false 14 | 15 | self.public_send(param_symbol).each do |param| 16 | errors = duplicate_key?(keys, param) 17 | end 18 | 19 | self.errors.add(param_symbol, _('Please ensure the following parameters are unique')) if errors 20 | end 21 | end 22 | 23 | def duplicate_key?(keys, param) 24 | if keys.include?(param.key) 25 | param.errors.add(:key, _('has already been taken')) 26 | return true 27 | else 28 | keys << param.key 29 | end 30 | 31 | false 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/models/container.rb: -------------------------------------------------------------------------------- 1 | class Container < ApplicationRecord 2 | include Authorizable 3 | include Taxonomix 4 | 5 | belongs_to :compute_resource 6 | belongs_to :registry, :class_name => 'DockerRegistry', :foreign_key => :registry_id 7 | has_many :environment_variables, :dependent => :destroy, :foreign_key => :reference_id, 8 | :inverse_of => :container, 9 | :class_name => 'EnvironmentVariable' 10 | 11 | accepts_nested_attributes_for :environment_variables, :allow_destroy => true 12 | 13 | has_many :exposed_ports, :dependent => :destroy, :foreign_key => :reference_id, 14 | :inverse_of => :container, 15 | :class_name => 'ExposedPort' 16 | 17 | has_many :dns, :dependent => :destroy, :foreign_key => :reference_id, 18 | :inverse_of => :container, 19 | :class_name => 'ForemanDocker::Dns' 20 | 21 | include ForemanDocker::ParameterValidators 22 | 23 | accepts_nested_attributes_for :exposed_ports, :allow_destroy => true 24 | scoped_search :on => :name 25 | 26 | validates :name, :uniqueness => { :scope => :compute_resource_id } 27 | 28 | def repository_pull_url 29 | repo = tag.blank? ? repository_name : "#{repository_name}:#{tag}" 30 | repo = registry.prefixed_url(repo) if registry 31 | repo 32 | end 33 | 34 | def parametrize 35 | { 'name' => name, # key has to be lower case to be picked up by the Docker API 36 | 'Image' => repository_pull_url, 37 | 'Tty' => tty, 38 | 'Memory' => ::ForemanDocker::Utility.parse_memory(memory), 39 | 'Entrypoint' => entrypoint.try(:split), 'Cmd' => command.try(:split), 40 | 'AttachStdout' => attach_stdout, 'AttachStdin' => attach_stdin, 41 | 'AttachStderr' => attach_stderr, 'CpuShares' => cpu_shares, 42 | 'Cpuset' => cpu_set, 43 | 'Env' => environment_variables.map { |env| "#{env.key}=#{env.value}" }, 44 | 'ExposedPorts' => Hash[*exposed_ports.map { |v| [v.key + "/" + v.value, {}] }.flatten], 45 | 'HostConfig' => { 46 | 'Dns' => dns.map { |env| "#{env.key}" } 47 | } } 48 | end 49 | 50 | def in_fog 51 | @fog_container ||= compute_resource.vms.get(uuid) 52 | end 53 | 54 | def self.humanize_class_name(_name = nil) 55 | _("Docker/Container") 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_state.rb: -------------------------------------------------------------------------------- 1 | class DockerContainerWizardState < ApplicationRecord 2 | has_one :preliminary, :class_name => 'DockerContainerWizardStates::Preliminary', 3 | :dependent => :destroy, :validate => true, :autosave => true 4 | has_one :image, :class_name => 'DockerContainerWizardStates::Image', 5 | :dependent => :destroy, :validate => true, :autosave => true 6 | has_one :configuration, :class_name => 'DockerContainerWizardStates::Configuration', 7 | :dependent => :destroy, :validate => true, :autosave => true 8 | has_one :environment, :class_name => 'DockerContainerWizardStates::Environment', 9 | :dependent => :destroy, :validate => true, :autosave => true 10 | 11 | delegate :compute_resource_id, :to => :preliminary 12 | delegate :compute_resource, :to => :preliminary 13 | 14 | delegate :environment_variables, :to => :environment 15 | delegate :exposed_ports, :to => :environment 16 | delegate :dns, :to => :environment 17 | 18 | def container_attributes 19 | { :repository_name => image.repository_name, 20 | :tag => image.tag, 21 | :registry_id => image.registry_id, 22 | :name => configuration.name, 23 | :compute_resource_id => preliminary.compute_resource_id, 24 | :tty => environment.tty, 25 | :memory => configuration.memory, 26 | :entrypoint => configuration.entrypoint, 27 | :command => configuration.command, 28 | :attach_stdout => environment.attach_stdout, 29 | :attach_stdin => environment.attach_stdin, 30 | :attach_stderr => environment.attach_stderr, 31 | :cpu_shares => configuration.cpu_shares, 32 | :cpu_set => configuration.cpu_set 33 | } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/configuration.rb: -------------------------------------------------------------------------------- 1 | module DockerContainerWizardStates 2 | class Configuration < ApplicationRecord 3 | self.table_name_prefix = 'docker_container_wizard_states_' 4 | belongs_to :wizard_state, :class_name => 'DockerContainerWizardState', 5 | :foreign_key => :docker_container_wizard_state_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/dns.rb: -------------------------------------------------------------------------------- 1 | require 'resolv' 2 | 3 | module DockerContainerWizardStates 4 | class Dns < DockerParameter 5 | belongs_to :environment, :foreign_key => :reference_id, 6 | :inverse_of => :dns, 7 | :class_name => 'DockerContainerWizardStates::Environment' 8 | validates :key, :uniqueness => { :scope => :reference_id }, 9 | :format => { 10 | :with => Regexp.union(Resolv::IPv4::Regex, 11 | Resolv::IPv6::Regex, 12 | /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}$/) } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/environment.rb: -------------------------------------------------------------------------------- 1 | module DockerContainerWizardStates 2 | class Environment < ApplicationRecord 3 | self.table_name_prefix = 'docker_container_wizard_states_' 4 | belongs_to :wizard_state, :class_name => 'DockerContainerWizardState' 5 | 6 | has_many :environment_variables, :dependent => :destroy, :foreign_key => :reference_id, 7 | :inverse_of => :environment, 8 | :class_name => 9 | 'DockerContainerWizardStates::EnvironmentVariable' 10 | 11 | has_many :exposed_ports, :dependent => :destroy, :foreign_key => :reference_id, 12 | :inverse_of => :environment, 13 | :class_name => 'DockerContainerWizardStates::ExposedPort' 14 | has_many :dns, :dependent => :destroy, :foreign_key => :reference_id, 15 | :inverse_of => :environment, 16 | :class_name => 'DockerContainerWizardStates::Dns' 17 | 18 | include ForemanDocker::ParameterValidators 19 | accepts_nested_attributes_for :environment_variables, :allow_destroy => true 20 | accepts_nested_attributes_for :exposed_ports, :allow_destroy => true 21 | accepts_nested_attributes_for :dns, :allow_destroy => true 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/environment_variable.rb: -------------------------------------------------------------------------------- 1 | module DockerContainerWizardStates 2 | class EnvironmentVariable < DockerParameter 3 | belongs_to :environment, :foreign_key => :reference_id, :inverse_of => :environment_variables, 4 | :class_name => 'DockerContainerWizardStates::Environment' 5 | validates :key, :uniqueness => { :scope => :reference_id } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/exposed_port.rb: -------------------------------------------------------------------------------- 1 | module DockerContainerWizardStates 2 | class ExposedPort < DockerParameter 3 | belongs_to :environment, :foreign_key => :reference_id, :inverse_of => :exposed_ports, 4 | :class_name => 'DockerContainerWizardStates::Environment' 5 | validates :key, :uniqueness => { :scope => :reference_id }, 6 | :numericality => { :only_integer => true, 7 | :greater_than => 0, 8 | :less_than_or_equal_to => 655_35 } 9 | validates :value, :presence => true, 10 | :inclusion => { :in => %w(tcp udp) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/image.rb: -------------------------------------------------------------------------------- 1 | module DockerContainerWizardStates 2 | class Image < ApplicationRecord 3 | self.table_name_prefix = 'docker_container_wizard_states_' 4 | belongs_to :wizard_state, :class_name => 'DockerContainerWizardState', 5 | :foreign_key => :docker_container_wizard_state_id 6 | delegate :compute_resource_id, :to => :wizard_state 7 | delegate :compute_resource, :to => :wizard_state 8 | 9 | validates :tag, :presence => true 10 | validates :repository_name, :presence => true 11 | validate :image_exists 12 | 13 | def name 14 | "#{repository_name}:#{tag}" 15 | end 16 | 17 | def registry_api 18 | if registry_id 19 | DockerRegistry.find(registry_id).api 20 | else 21 | Service::RegistryApi.docker_hub 22 | end 23 | end 24 | 25 | def sources 26 | [compute_resource, registry_api] 27 | end 28 | 29 | def image_search_service 30 | ForemanDocker::ImageSearch.new(*sources) 31 | end 32 | 33 | def image_exists 34 | return true if image_search_service.available?(name) 35 | error_msg = _("Container image %{image_name} is not available.") % { 36 | image_name: "#{name}", 37 | } 38 | errors.add(:image, error_msg) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/models/docker_container_wizard_states/preliminary.rb: -------------------------------------------------------------------------------- 1 | module DockerContainerWizardStates 2 | class Preliminary < ApplicationRecord 3 | include Taxonomix 4 | 5 | self.table_name_prefix = 'docker_container_wizard_states_' 6 | belongs_to :wizard_state, :class_name => 'DockerContainerWizardState', 7 | :foreign_key => :docker_container_wizard_state_id 8 | 9 | belongs_to :compute_resource, :required => true 10 | 11 | def used_location_ids 12 | Location.joins(:taxable_taxonomies).where( 13 | 'taxable_taxonomies.taxable_type' => 'DockerContainerWizardStates::Preliminary', 14 | 'taxable_taxonomies.taxable_id' => id).pluck("#{Taxonomy.table_name}.id") 15 | end 16 | 17 | def used_organization_ids 18 | Organization.joins(:taxable_taxonomies).where( 19 | 'taxable_taxonomies.taxable_type' => 'DockerContainerWizardStates::Preliminary', 20 | 'taxable_taxonomies.taxable_id' => id).pluck("#{Taxonomy.table_name}.id") 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/docker_parameter.rb: -------------------------------------------------------------------------------- 1 | class DockerParameter < ApplicationRecord 2 | extend FriendlyId 3 | friendly_id :key 4 | include Parameterizable::ByIdName 5 | 6 | validates_lengths_from_database 7 | 8 | include Authorizable 9 | validates :key, :presence => true, :no_whitespace => true 10 | 11 | scoped_search :on => :key, :complete_value => true 12 | 13 | default_scope -> { order("docker_parameters.key") } 14 | 15 | before_validation :strip_whitespaces 16 | 17 | def strip_whitespaces 18 | self.value.strip! unless value.blank? 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/docker_registry.rb: -------------------------------------------------------------------------------- 1 | class DockerRegistry < ApplicationRecord 2 | include Authorizable 3 | include Taxonomix 4 | include Encryptable 5 | 6 | default_scope do 7 | with_taxonomy_scope do 8 | order('docker_registries.name') 9 | end 10 | end 11 | 12 | has_many :containers, :foreign_key => "registry_id", :dependent => :destroy 13 | encrypts :password 14 | 15 | validates_lengths_from_database 16 | validates :name, :presence => true, :uniqueness => true 17 | validates :url, :presence => true, :uniqueness => true, 18 | :url_schema => ['http', 'https'] 19 | validate :attempt_login 20 | 21 | scoped_search :on => :name, :complete_value => true 22 | scoped_search :on => :url 23 | 24 | def used_location_ids 25 | Location.joins(:taxable_taxonomies).where( 26 | 'taxable_taxonomies.taxable_type' => 'DockerRegistry', 27 | 'taxable_taxonomies.taxable_id' => id).pluck("#{Taxonomy.table_name}.id") 28 | end 29 | 30 | def used_organization_ids 31 | Organization.joins(:taxable_taxonomies).where( 32 | 'taxable_taxonomies.taxable_type' => 'DockerRegistry', 33 | 'taxable_taxonomies.taxable_id' => id).pluck("#{Taxonomy.table_name}.id") 34 | end 35 | 36 | def prefixed_url(image_name) 37 | uri = URI(url) 38 | "#{uri.hostname}:#{uri.port}/#{image_name}" 39 | end 40 | 41 | def self.humanize_class_name(_name = nil) 42 | _("Docker/Registry") 43 | end 44 | 45 | def api 46 | @api ||= Service::RegistryApi.new(url: url, user: username, 47 | password: password, verify_ssl: verify_ssl) 48 | end 49 | 50 | private 51 | 52 | def attempt_login 53 | api.ok? 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) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /app/models/environment_variable.rb: -------------------------------------------------------------------------------- 1 | class EnvironmentVariable < DockerParameter 2 | belongs_to :container, :foreign_key => :reference_id, :inverse_of => :environment_variables 3 | audited :associated_with => :container, :allow_mass_assignment => true 4 | validates :key, :uniqueness => { :scope => :reference_id } 5 | end 6 | -------------------------------------------------------------------------------- /app/models/exposed_port.rb: -------------------------------------------------------------------------------- 1 | class ExposedPort < DockerParameter 2 | belongs_to :container, :foreign_key => :reference_id, :inverse_of => :exposed_ports 3 | audited :associated_with => :container, :allow_mass_assignment => true 4 | validates :key, :uniqueness => { :scope => :reference_id } 5 | validates :key, :numericality => { :only_integer => true, 6 | :greater_than => 0, 7 | :less_than_or_equal_to => 655_35, 8 | :message => "%{value} is not a valid port number" } 9 | 10 | validates :value, :presence => true, 11 | :inclusion => { :in => %w(tcp udp), 12 | :message => "%{value} is not a valid protocol" } 13 | end 14 | -------------------------------------------------------------------------------- /app/models/foreman_docker/compute_resource_extensions.rb: -------------------------------------------------------------------------------- 1 | module ForemanDocker 2 | module ComputeResourceExtensions 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | def self.providers_requiring_url 7 | _("Docker, Libvirt, oVirt, OpenStack and Rackspace") 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/foreman_docker/dns.rb: -------------------------------------------------------------------------------- 1 | require 'resolv' 2 | 3 | module ForemanDocker 4 | class Dns < DockerParameter 5 | belongs_to :container, :foreign_key => :reference_id, 6 | :inverse_of => :dns, 7 | :class_name => "Container" 8 | 9 | audited :associated_with => :container, :allow_mass_assignment => true 10 | validates :key, :uniqueness => { :scope => :reference_id }, 11 | :format => { 12 | :with => Regexp.union(Resolv::IPv4::Regex, 13 | Resolv::IPv6::Regex, 14 | /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}$/) } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/foreman_docker/docker.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | module ForemanDocker 4 | class Docker < ::ComputeResource 5 | validates :url, :format => { :with => URI.regexp }, :presence => true 6 | validates :email, :format => { :with => /.+@.+\..+/i }, :allow_blank => true 7 | 8 | def self.model_name 9 | ComputeResource.model_name 10 | end 11 | 12 | def self.get_container(container) 13 | conn = container.compute_resource.docker_connection 14 | ::Docker::Container.get(container.uuid, {}, conn) 15 | end 16 | 17 | def capabilities 18 | [] 19 | end 20 | 21 | def supports_update? 22 | false 23 | end 24 | 25 | def provided_attributes 26 | super.merge(:mac => :mac) 27 | end 28 | 29 | def max_memory 30 | 16 * 1024 * 1024 * 1024 31 | end 32 | 33 | def max_cpu_count 34 | ::Docker.info['NCPU'] || 1 35 | end 36 | 37 | def available_images 38 | ::Docker::Image.all({}, docker_connection) 39 | end 40 | 41 | def local_images(filter = '') 42 | ::Docker::Image.all({ 'filter' => filter }, docker_connection) 43 | end 44 | 45 | def tags_for_local_image(image, tag = nil) 46 | result = image.info['RepoTags'].map do |image_tag| 47 | _, tag = image_tag.split(':') 48 | tag 49 | end 50 | result = filter_tags(result, tag) if tag 51 | result 52 | end 53 | 54 | def exist?(name) 55 | ::Docker::Image.exist?(name, {}, docker_connection) 56 | end 57 | 58 | def image(id) 59 | ::Docker::Image.get(id, {}, docker_connection) 60 | end 61 | 62 | def tags(image_name) 63 | if exist?(image_name) 64 | tags_for_local_image(image(image_name)) 65 | else 66 | Service::RegistryApi.docker_hub.tags(image_name).map { |tag| tag['name'] } 67 | end 68 | end 69 | 70 | def search(term = '') 71 | client.images.image_search(:term => term) 72 | end 73 | 74 | def provider_friendly_name 75 | 'Docker' 76 | end 77 | 78 | def create_container(args = {}) 79 | options = vm_instance_defaults.merge(args) 80 | logger.debug("Creating container with the following options: #{options.inspect}") 81 | docker_command do 82 | ::Docker::Container.create(options, docker_connection) 83 | end 84 | end 85 | 86 | def create_image(args = {}) 87 | logger.debug("Creating docker image with the following options: #{args.inspect}") 88 | docker_command do 89 | ::Docker::Image.create(args, credentials, docker_connection) 90 | end 91 | end 92 | 93 | def vm_instance_defaults 94 | ActiveSupport::HashWithIndifferentAccess.new('name' => "foreman_#{Time.now.to_i}", 95 | 'Cmd' => ['/bin/bash']) 96 | end 97 | 98 | def console(uuid) 99 | test_connection 100 | container = ::Docker::Container.get(uuid, {}, docker_connection) 101 | { 102 | :name => container.info['Name'], 103 | 'timestamp' => Time.now.utc, 104 | 'output' => container.logs(:stdout => true, :tail => 100) 105 | } 106 | end 107 | 108 | def api_version 109 | ::Docker.version(docker_connection) 110 | end 111 | 112 | def authenticate! 113 | ::Docker.authenticate!(credentials, docker_connection) 114 | end 115 | 116 | def test_connection(options = {}) 117 | super 118 | api_version 119 | credentials.empty? ? true : authenticate! 120 | # This should only rescue Fog::Errors, but Fog returns all kinds of errors... 121 | rescue => e 122 | errors[:base] << e.message 123 | false 124 | end 125 | 126 | def docker_connection 127 | @docker_connection ||= ::Docker::Connection.new(url, credentials) 128 | end 129 | 130 | protected 131 | 132 | def filter_tags(result, query) 133 | result.select do |tag_name| 134 | tag_name['name'] =~ /^#{query}/ 135 | end 136 | end 137 | 138 | def docker_command 139 | yield 140 | rescue Excon::Errors::Error, ::Docker::Error::DockerError => e 141 | logger.debug "Fog error: #{e.message}\n " + e.backtrace.join("\n ") 142 | errors.add(:base, 143 | _("Error creating communicating with Docker. Check the Foreman logs: %s") % 144 | e.message.to_s) 145 | false 146 | end 147 | 148 | def bootstrap(args) 149 | client.servers.bootstrap vm_instance_defaults.merge(args.to_hash) 150 | rescue Fog::Errors::Error => e 151 | errors.add(:base, e.to_s) 152 | false 153 | end 154 | 155 | def client 156 | opts = { 157 | :provider => 'fogdocker', 158 | :docker_url => url 159 | } 160 | opts[:docker_username] = user if user.present? 161 | opts[:docker_password] = password if password.present? 162 | opts[:docker_email] = email if email.present? 163 | @client ||= ::Fog::Compute.new(opts) 164 | end 165 | 166 | def credentials 167 | @credentials ||= {}.tap do |options| 168 | options[:username] = user if user.present? 169 | options[:password] = password if password.present? 170 | options[:email] = email if email.present? 171 | end 172 | end 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /app/models/foreman_docker/taxonomy_extensions.rb: -------------------------------------------------------------------------------- 1 | module ForemanDocker 2 | module TaxonomyExtensions 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | if SETTINGS[:version].to_s.to_f <= 1.7 7 | def self.enabled_taxonomies 8 | %w(locations organizations).select { |taxonomy| SETTINGS["#{taxonomy}_enabled".to_sym] } 9 | end 10 | end 11 | end 12 | end 13 | end 14 | # To be removed after 1.7 compatibility is no longer required 15 | -------------------------------------------------------------------------------- /app/models/service/containers.rb: -------------------------------------------------------------------------------- 1 | module Service 2 | class Containers 3 | def errors 4 | @errors ||= [] 5 | end 6 | 7 | def start_container!(wizard_state) 8 | ActiveRecord::Base.transaction do 9 | container = create_container_object(wizard_state) 10 | container.save! 11 | run_container(container) 12 | destroy_wizard_state(wizard_state) 13 | container 14 | end 15 | end 16 | 17 | def create_container!(wizard_state) 18 | ActiveRecord::Base.transaction do 19 | container = create_container_object(wizard_state) 20 | container.save! 21 | destroy_wizard_state(wizard_state) 22 | container 23 | end 24 | end 25 | 26 | def create_container_object(wizard_state) 27 | container = Container.new do |r| 28 | r.attributes = wizard_state.container_attributes 29 | # eagerly load environment variables and exposed ports configuration 30 | state = DockerContainerWizardState.includes( 31 | :environment => [:environment_variables, :exposed_ports]).find(wizard_state.id) 32 | 33 | load_environment_variables(state, r) 34 | load_exposed_ports(state, r) 35 | load_dns(state, r) 36 | end 37 | 38 | Taxonomy.enabled_taxonomies.each do |taxonomy| 39 | container.send(:"#{taxonomy}=", wizard_state.preliminary.send(:"#{taxonomy}")) 40 | end 41 | 42 | pull_image(container) 43 | start_container(container) 44 | unless container.valid? 45 | @errors = errors + container.errors.full_messages 46 | end 47 | 48 | if @errors.present? 49 | @errors = @errors.flatten.uniq 50 | fail ActiveRecord::Rollback 51 | end 52 | 53 | container.name = container.in_fog.name[1..-1] unless container.name.present? 54 | 55 | container 56 | end 57 | 58 | def pull_image(container) 59 | success = container.compute_resource. 60 | create_image(:fromImage => container.repository_pull_url) 61 | return true if success 62 | @errors = errors + container.compute_resource.errors.full_messages 63 | end 64 | 65 | def start_container(container) 66 | started = container.compute_resource.create_container(container.parametrize) 67 | if started 68 | container.uuid = started.id 69 | else 70 | @errors = errors + container.compute_resource.errors.full_messages 71 | end 72 | started 73 | end 74 | 75 | def destroy_wizard_state(wizard_state) 76 | wizard_state.destroy 77 | DockerContainerWizardState.where(["updated_at < ?", (Time.now.utc - 24.hours)]).destroy_all 78 | end 79 | 80 | def load_environment_variables(state, r) 81 | state.environment_variables.each do |environment_variable| 82 | var = r.environment_variables.build 83 | var.key = environment_variable.key 84 | var.value = environment_variable.value 85 | end 86 | end 87 | 88 | def load_exposed_ports(state, r) 89 | state.exposed_ports.each do |e| 90 | port = r.exposed_ports.build 91 | port.key = e.key 92 | port.value = e.value 93 | end 94 | end 95 | 96 | def load_dns(state, r) 97 | state.dns.each do |e| 98 | dns = r.dns.build 99 | dns.key = e.key 100 | end 101 | end 102 | 103 | def full_messages 104 | return errors.full_messages if errors.respond_to?(:full_messages) 105 | @errors 106 | end 107 | 108 | def run_container(container) 109 | docker_container = container.compute_resource.find_vm_by_uuid(container.uuid) 110 | error(_('Could not start container')) unless docker_container.send(:start) 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /app/models/service/registry_api.rb: -------------------------------------------------------------------------------- 1 | module Service 2 | class RegistryApi 3 | DOCKER_HUB = 'https://index.docker.io/'.freeze 4 | DEFAULTS = { 5 | url: 'http://localhost:5000'.freeze, 6 | connection: { omit_default_port: true, 7 | headers: { "Content-Type" => "application/json" } 8 | } 9 | } 10 | 11 | attr_accessor :config, :url 12 | delegate :logger, :to => Rails 13 | 14 | def initialize(params = {}) 15 | self.config = DEFAULTS.merge(params) 16 | self.url = config[:url] 17 | 18 | Docker.logger = logger if Rails.env.development? || Rails.env.test? 19 | end 20 | 21 | def connection 22 | @connection ||= ::Docker::Connection.new(url, default_connection_options.merge(credentials)) 23 | end 24 | 25 | def get(path, params = nil) 26 | response = connection.get('/'.freeze, params, 27 | connection.options.merge({ path: "#{path}" })) 28 | response = parse_json(response) 29 | response 30 | end 31 | 32 | # Since the Registry API v2 does not support a search the v1 endpoint is used 33 | # Newer registries will fail, the v2 catalog endpoint is used 34 | def search(query) 35 | get('/v1/search'.freeze, { q: query }) 36 | rescue => e 37 | logger.warn "API v1 - Search failed #{e.backtrace}" 38 | { 'results' => catalog(query) } 39 | end 40 | 41 | # Some Registries might have this endpoint not implemented/enabled 42 | def catalog(query) 43 | get('/v2/_catalog'.freeze)['repositories'].select do |image| 44 | image =~ /^#{query}/ 45 | end.map { |image_name| { 'name' => image_name } } 46 | end 47 | 48 | def tags(image_name, query = nil) 49 | result = get_tags(image_name) 50 | result = result.keys.map { |t| {'name' => t.to_s } } if result.is_a? Hash 51 | result = filter_tags(result, query) if query 52 | result 53 | end 54 | 55 | def ok? 56 | get('/v1/'.freeze).match("Docker Registry API") 57 | rescue => e 58 | logger.warn "API v1 - Ping failed #{e.backtrace}" 59 | get('/v2/'.freeze).is_a? Hash 60 | end 61 | 62 | def self.docker_hub 63 | @@docker_hub ||= new(url: DOCKER_HUB) 64 | end 65 | 66 | private 67 | 68 | def default_connection_options 69 | @default_connection_options ||= DEFAULTS[:connection].tap do |defaults| 70 | defaults[:ssl_verify_peer] = config.fetch(:verify_ssl, true) 71 | end 72 | end 73 | 74 | def parse_json(string) 75 | JSON.parse(string) 76 | rescue => e 77 | logger.warn "JSON parsing failed: #{e.backtrace}" 78 | string 79 | end 80 | 81 | def get_tags(image_name) 82 | get("/v1/repositories/#{image_name}/tags") 83 | rescue => e 84 | Foreman::Logging.exception("API v1 - Repository images request failed", e) 85 | tags_v2(image_name) 86 | end 87 | 88 | def tags_v2(image_name) 89 | get("/v2/#{image_name}/tags/list")['tags'].map { |tag| { 'name' => tag } } 90 | rescue Docker::Error::NotFoundError 91 | [] 92 | rescue Excon::Error::Found => e 93 | if e.response.status == 302 94 | new_path = URI.parse(e.response.headers['Location']).path 95 | get(new_path)['tags'].map { |tag| { 'name' => tag } } 96 | else 97 | ::Foreman.exception "Could not load tags using v2", e 98 | [] 99 | end 100 | end 101 | 102 | def credentials 103 | { user: config.fetch(:user, nil), 104 | password: config.fetch(:password, nil) } 105 | end 106 | 107 | def filter_tags(result, query) 108 | result.select do |tag_name| 109 | tag_name['name'] =~ /^#{query}/ 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /app/overrides/remove_docker_from_compute_profiles.rb: -------------------------------------------------------------------------------- 1 | Deface::Override.new( 2 | :virtual_path => 'compute_profiles/show', 3 | :name => 'remove_docker_from_compute_profiles', 4 | :replace => "erb[silent]:contains('ComputeResource.authorized(:view_compute_resources)')", 5 | :text => "<% ComputeResource.where.not(:type => 'ForemanDocker::Docker'). 6 | authorized(:view_compute_resources).each do |compute_resource| %>" 7 | ) 8 | 9 | Deface::Override.new( 10 | :virtual_path => 'compute_resources/show', 11 | :name => 'remove_compute_profiles_tab', 12 | :replace => 'a[href="#compute_profiles"]', 13 | :text => "<%= link_to(_('Compute profiles'), '#compute_profiles', :'data-toggle' => 'tab') unless @compute_resource.type == 'ForemanDocker::Docker' %>" 14 | ) 15 | -------------------------------------------------------------------------------- /app/overrides/rename_virtual_machines_containers.rb: -------------------------------------------------------------------------------- 1 | Deface::Override.new( 2 | :virtual_path => 'compute_resources/show', 3 | :name => 'rename_virtual_machines_containers', 4 | :replace => "erb[loud]:contains('Virtual Machines')", 5 | :text => "<%= if @compute_resource.type == 'ForemanDocker::Docker' 6 | _('Containers') 7 | else 8 | _('Virtual Machines') 9 | end %>" 10 | ) 11 | -------------------------------------------------------------------------------- /app/services/foreman_docker/container_remover.rb: -------------------------------------------------------------------------------- 1 | module ForemanDocker 2 | module ContainerRemover 3 | module_function 4 | 5 | def remove_unmanaged(compute_resource_id, uuid) 6 | deleted_identifier = uuid 7 | 8 | ComputeResource. 9 | authorized(:destroy_compute_resources_vms). 10 | find(compute_resource_id). 11 | destroy_vm(uuid) 12 | 13 | deleted_identifier 14 | rescue => error 15 | Rails.logger. 16 | error "#{error.message} (#{error.class})\n#{error.backtrace.join("\n")}" 17 | false 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/services/foreman_docker/image_search.rb: -------------------------------------------------------------------------------- 1 | module ForemanDocker 2 | class ImageSearch 3 | def initialize(*args) 4 | @sources = {} 5 | args.each do |source| 6 | add_source(source) 7 | end 8 | end 9 | 10 | def add_source(source) 11 | case source 12 | when ForemanDocker::Docker 13 | @sources[:compute_resource] ||= [] 14 | @sources[:compute_resource] << source 15 | when Service::RegistryApi 16 | @sources[:registry] ||= [] 17 | @sources[:registry] << source 18 | end 19 | end 20 | 21 | def remove_source(source) 22 | @sources.each do |_, sources| 23 | sources.delete(source) 24 | end 25 | end 26 | 27 | def search(query) 28 | return [] if query[:term].blank? || query[:term] == ':' 29 | 30 | unless query[:tags] == 'true' 31 | images(query[:term]) 32 | else 33 | tags(query[:term]) 34 | end 35 | end 36 | 37 | def images(query) 38 | sources_results_for(:search, query) 39 | end 40 | 41 | def tags(query) 42 | image_name, tag = query.split(':') 43 | sources_results_for(:tags, image_name, tag) 44 | .map { |tag_name| { 'name' => tag_name } } 45 | end 46 | 47 | def available?(query) 48 | tags(query).present? 49 | end 50 | 51 | private 52 | 53 | def registry_search(registry, term) 54 | registry.search(term)['results'] 55 | end 56 | 57 | def compute_resource_search(compute_resource, query) 58 | images = compute_resource.local_images(query) 59 | images.flat_map do |image| 60 | image.info['RepoTags'].map do |tag| 61 | { 'name' => tag.split(':').first } 62 | end 63 | end.uniq 64 | end 65 | 66 | def compute_resource_image(compute_resource, image_name) 67 | compute_resource.image(image_name) 68 | rescue ::Docker::Error::NotFoundError 69 | nil 70 | end 71 | 72 | def compute_resource_tags(compute_resource, image_name, tag) 73 | image = compute_resource_image(compute_resource, image_name) 74 | image ? compute_resource.tags_for_local_image(image, tag) : [] 75 | end 76 | 77 | def registry_tags(registry, image_name, tag) 78 | registry.tags(image_name, tag).map { |t| t['name'] } 79 | end 80 | 81 | def sources_results_for(search, *args) 82 | result = [] 83 | @sources.each do |kind, sources| 84 | sources.each do |source| 85 | result << self.send("#{kind}_#{search}", source, *args) 86 | end 87 | end 88 | result.flatten.compact 89 | end 90 | end 91 | end 92 | 93 | -------------------------------------------------------------------------------- /app/services/foreman_docker/utility.rb: -------------------------------------------------------------------------------- 1 | module ForemanDocker 2 | module Utility 3 | def self.parse_memory(mem) 4 | return 0 unless mem.present? 5 | mem.gsub!(/\s/, '') 6 | return mem.to_i if mem[/^\d*$/] # Return if size is without unit 7 | size, unit = mem.match(/^(\d+)([a-zA-Z])$/)[1, 2] 8 | case unit.downcase 9 | when 'g' 10 | size.to_i * 1024 * 1024 * 1024 11 | when 'm' 12 | size.to_i * 1024 * 1024 13 | when 'k' 14 | size.to_i * 1024 15 | else 16 | fail "Unknown size unit '#{unit}'" 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/api/v1/compute_resources/docker.json: -------------------------------------------------------------------------------- 1 | attributes :user 2 | -------------------------------------------------------------------------------- /app/views/api/v2/compute_resources/docker.json: -------------------------------------------------------------------------------- 1 | attributes :user 2 | -------------------------------------------------------------------------------- /app/views/api/v2/containers/base.json.rabl: -------------------------------------------------------------------------------- 1 | object @container 2 | 3 | attributes :id, :name, :uuid 4 | -------------------------------------------------------------------------------- /app/views/api/v2/containers/index.json.rabl: -------------------------------------------------------------------------------- 1 | collection @containers 2 | 3 | extends 'api/v2/containers/main' 4 | -------------------------------------------------------------------------------- /app/views/api/v2/containers/main.json.rabl: -------------------------------------------------------------------------------- 1 | object @container 2 | 3 | extends 'api/v2/containers/base' 4 | 5 | attributes :command, :compute_resource_id, :compute_resource_name, :entrypoint, 6 | :cpu_set, :cpu_shares, :memory, :tty, 7 | :attach_stdin, :attach_stdout, :attach_stderr, 8 | :repository_name, :tag, :registry_id, :registry_name, 9 | :created_at, :updated_at 10 | -------------------------------------------------------------------------------- /app/views/api/v2/containers/show.json.rabl: -------------------------------------------------------------------------------- 1 | object @container 2 | 3 | extends "api/v2/containers/main" 4 | 5 | node do |container| 6 | partial("api/v2/taxonomies/children_nodes", :object => container) 7 | end 8 | -------------------------------------------------------------------------------- /app/views/api/v2/registries/base.json.rabl: -------------------------------------------------------------------------------- 1 | object @registry 2 | 3 | attributes :id, :name 4 | -------------------------------------------------------------------------------- /app/views/api/v2/registries/index.json.rabl: -------------------------------------------------------------------------------- 1 | collection @registries 2 | 3 | extends 'api/v2/registries/main' 4 | -------------------------------------------------------------------------------- /app/views/api/v2/registries/main.json.rabl: -------------------------------------------------------------------------------- 1 | object @registry 2 | 3 | extends 'api/v2/registries/base' 4 | 5 | attributes :url, :description, :created_at, :updated_at, :username 6 | 7 | -------------------------------------------------------------------------------- /app/views/api/v2/registries/show.json.rabl: -------------------------------------------------------------------------------- 1 | object @registry 2 | 3 | extends "api/v2/registries/main" 4 | 5 | node do |registry| 6 | partial("api/v2/taxonomies/children_nodes", :object => registry) 7 | end 8 | -------------------------------------------------------------------------------- /app/views/compute_resources/form/_docker.html.erb: -------------------------------------------------------------------------------- 1 | <%= text_f f, :url, :size => "col-md-8", :help_block => _("e.g. https://docker.example.com:4243 or unix:///var/run/docker.sock") %> 2 | <%= text_f f, :user %> 3 | <%= password_f f, :password %> 4 | <%= text_f f, :email %> 5 | 6 | <% authenticated = f.object.authenticate! rescue false %> 7 | <%= link_to_function _("Test Connection"), "testConnection(this)", 8 | :class => "btn + #{authenticated ? "btn-success" : "btn-default"}", 9 | :'data-url' => test_connection_compute_resources_path %> 10 | 11 | <%= spinner('', :id => 'test_connection_indicator', :class => 'hide') %> 12 | -------------------------------------------------------------------------------- /app/views/compute_resources/show/_docker.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= _("URL") %> 3 | <%= @compute_resource.url %> 4 | 5 | -------------------------------------------------------------------------------- /app/views/compute_resources_vms/form/_docker.html.erb: -------------------------------------------------------------------------------- 1 | <% javascript 'compute_resource', 'lookup_keys' %> 2 | <%= text_f f, :name if show_vm_name? %> 3 | <% new = @host.nil? || @host.try(:new_record?) %> 4 | <%= select_f f, :image, compute_resource.images, :uuid, :name, {}, 5 | {:help_inline => :indicator, 6 | :label => _('Image') } %> 7 | <%= f.hidden_field :template if !new %> 8 | 9 |
10 | <%= selectable_f f, :cores, 1..compute_resource.max_cpu_count, { }, :class => "col-md-2", :label => _('Cores') %> 11 | <%= selectable_f f, :memory, memory_options(compute_resource.max_memory), { }, :class => "col-md-2", :label => _('Memory') %> 12 |
13 | <%= text_f f, :cmd, :label => _('Command') %> 14 | <% checked = params[:host] && params[:host][:compute_attributes] && params[:host][:compute_attributes][:start] || '1' %> 15 | <%= checkbox_f f, :start, { :checked => (checked == '1'), :help_inline => _("Power ON this machine"), :label => _('Start') } if new && controller_name != "compute_attributes" %> 16 | -------------------------------------------------------------------------------- /app/views/compute_resources_vms/index/_docker.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= _('Name') %> 4 | <%= _('Image') %> 5 | <%= _('CPUs') %> 6 | <%= _('Memory') %> 7 | <%= _('Status') %> 8 | <%= _('Power') %> 9 | <%= _('Action') %> 10 | 11 | 12 | 13 | <% @vms.each do |vm| %> 14 | 15 | <% name = vm.name || (!vm.attributes['names'].empty? && vm.attributes['names'].first) %> 16 | <%= link_to_if_authorized name, hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity) %> 17 | <%= vm.image %> 18 | <%= vm.cores %> 19 | <%= number_to_human_size vm.memory %> 20 | <%= vm.attributes['status'] %> 21 | > <%= vm_state(vm) %> 22 | 23 | <%= action_buttons(container_power_action(vm), 24 | display_delete_if_authorized(hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.id))) %> 25 | 26 | 27 | <% end %> 28 | 29 | -------------------------------------------------------------------------------- /app/views/compute_resources_vms/show/_docker.html.erb: -------------------------------------------------------------------------------- 1 | <% title @vm.name %> 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
<%= _('Properties') %>
<%= _('Name') %><%= @vm.name %>
<%= _('IP Address') %><%= @vm.ipaddress %>
<%= _('CPU shares') %><%= @vm.cores %>
<%= _('UUID') %><%= @vm.identity %>
<%= _('Memory') %><%= number_to_human_size @vm.memory %>
<%= _('Command') %><%= @vm.command %>
<%= _('Exposed ports') %><%= @vm.exposed_ports %>
<%= _('Running on') %><%= link_to @compute_resource, compute_resource_path(@compute_resource) %>
39 |
40 | -------------------------------------------------------------------------------- /app/views/containers/_list.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% containers.each do |container| %> 17 | 18 | 19 | 21 | 22 | 23 | 26 | 27 | 28 | <% @compute_resource = resource %> 29 | 38 | 39 | <% end %> 40 | 41 |
<%= _("Name") %><%= _('Action') %>
<%= link_to_container(container, resource) %> 30 | <%= action_buttons(container_power_action(container), 31 | display_delete_if_authorized(hash_for_container_path( 32 | :compute_resource_id => resource, 33 | :id => container.id). 34 | merge(:auth_object => resource, :auth_action => 'destroy', 35 | :authorizer => authorizer), 36 | :confirm => _("Delete %s?") % container.name)) %> 37 |
42 | -------------------------------------------------------------------------------- /app/views/containers/index.html.erb: -------------------------------------------------------------------------------- 1 | <% title _("Containers") %> 2 | 3 | <%= title_actions(new_link(_("Create container"))) %> 4 | 5 | 14 | 15 |
16 | <% @container_resources.each_with_index do |resource, i| %> 17 | <% if i == 0 %> 18 |
19 | <% else %> 20 |
21 | <% end %> 22 | <% if resource.test_connection %> 23 | <%= render 'list', :containers => resource.vms, 24 | :resource => resource %> 25 | <% else %> 26 |
27 | <%= (_("Error connecting with the compute resource: %s ") % resource.errors.messages[:base]).html_safe %> 28 |
29 | <% end %> 30 |
31 | <% end %> 32 |
33 |
34 | -------------------------------------------------------------------------------- /app/views/containers/show.html.erb: -------------------------------------------------------------------------------- 1 | <% title "#{@container.name.titleize} - #{@container.in_fog.name}" %> 2 | <%= stylesheet 'foreman_docker/terminal' %> 3 | <%= container_title_actions(@container) %> 4 |
5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 59 | 60 | 61 | 62 | 67 | 68 | 69 | 70 | 81 | 82 | <% Taxonomy.enabled_taxonomies.each do |taxonomy| %> 83 | 84 | 85 | 86 | 87 | <% end %> 88 | 89 | 90 | 92 | 93 | 94 |
<%= _('Properties') %>
<%= _('Name') %><%= @container.in_fog.name %>
<%= _('Image Repository') %><%= @container.repository_name %>
<%= _('Image Tag') %><%= @container.tag %>
<%= _('IP Address') %><%= @container.in_fog.ipaddress %>
<%= _('CPU shares') %><%= @container.in_fog.cpu_shares %>
<%= _('CPU set') %><%= @container.cpu_set %>
<%= _('UUID') %><%= trunc_with_tooltip @container.in_fog.identity %>
<%= _('Memory') %><%= number_to_human_size @container.in_fog.memory %>
<%= _('Command') %><%= @container.in_fog.command %>
<%= _('Exposed ports') %> 49 | 50 | <% (@container.in_fog.exposed_ports || []).each do |exposed_port| %> 51 | <% pair = exposed_port.first.split("/") %> 52 | 53 | 54 | 55 | 56 | <% end %> 57 |
<%= pair.first %><%= pair.second %>
58 |
<%= _('DNS') %> 63 | <% (@container.in_fog.attributes['host_config_dns'] || []).each do |dns| %> 64 | <%= dns %>
65 | <% end %> 66 |
<%= _('Environment Variables') %> 71 | 72 | <% (@container.in_fog.environment_variables || []).each do |environment_variable| %> 73 | <% pair = environment_variable.split("=") %> 74 | 75 | 76 | 77 | 78 | <% end %> 79 |
<%= pair.first %><%= pair.second %>
80 |
<%= _(taxonomy.humanize) %><%= link_to_taxonomies(@container.send(:"#{taxonomy}")).html_safe %>
<%= _('Running on') %><%= link_to @container.compute_resource, 91 | compute_resource_path(@container.compute_resource) %>
95 |
96 |
97 |
98 |
99 | <% if @container.in_fog.ready? %> 100 | 110 |
111 |
112 |
113 | <% processes(@container).each do |process| %> 114 | 125 |
-index' class='collapse accordion-body'> 126 |
<%= JSON.pretty_generate(process) %>
127 |
128 | <% end %> 129 |
130 |
131 |
132 |

133 |           <%= logs(@container, :stdout => true, :tail => 100) %>
134 |         
135 |
136 |
137 | <% else %> 138 |
139 |

<%= _("Notice") %> 140 |


141 | <%= _("Your container is stopped.") %>

142 |

<%= _("Please turn on your container to see processes running, logs, and more.") %>

143 |
144 | <% end %> 145 |
146 |
147 | 148 |