Project

General

Profile

Revision 5274f6bc

Added by Daniel Lobato Garcia over 7 years ago

Fixes #7874 - Docker Containers API

Routes are namespaced, code still needs to be namespaced

View differences:

app/controllers/api/v2/containers_controller.rb
1
# module ForemanDocker
2
module Api
3
  module V2
4
    class ContainersController < ::Api::V2::BaseController
5
      before_filter :find_resource, :except => %w(index create)
6

  
7
      resource_description do
8
        resource_id 'containers'
9
        api_version 'v2'
10
        api_base_url '/api/v2'
11
      end
12

  
13
      api :GET, '/containers/', N_('List all containers')
14
      api :GET, '/compute_resources/:compute_resource_id/containers/',
15
          N_('List all containers in a compute resource')
16
      param :compute_resource_id, :identifier
17
      param_group :pagination, ::Api::V2::BaseController
18

  
19
      def index
20
        if params[:compute_resource_id].present?
21
          @containers = Container.where(:compute_resource_id => params[:compute_resource_id])
22
        else
23
          @containers = Container.all
24
        end
25
      end
26

  
27
      api :GET, '/containers/:id/', N_('Show a container')
28
      api :GET, '/compute_resources/:compute_resource_id/containers/:id',
29
          N_('Show container in a compute resource')
30
      param :id, :identifier, :required => true
31
      param :compute_resource_id, :identifier
32

  
33
      def show
34
      end
35

  
36
      def_param_group :container do
37
        param :container, Hash, :required => true, :action_aware => true do
38
          param :name, String, :required => true
39
          param_group :taxonomies, ::Api::V2::BaseController
40
          param :compute_resource_id, :identifier, :required => true
41
          param :registry_id, :identifier, :desc => N_('Registry this container will have to use
42
                                                        to get the image')
43
          param :image, String, :desc => N_('Image to use to create the container.
44
                                            Format should be repository:tag, e.g: centos:7')
45
          param :tty, :bool
46
          param :entrypoint, String
47
          param :cmd, String
48
          param :memory, String
49
          param :cpu_shares, :number
50
          param :cpu_sets, String
51
          param :environment_variables, Hash
52
          param :attach_stdout, :bool
53
          param :attach_stdin, :bool
54
          param :attach_stderr, :bool
55
          param :katello, :bool
56
        end
57
      end
58

  
59
      api :POST, '/containers/', N_('Create a container')
60
      api :POST, '/compute_resources/:compute_resource_id/containers/',
61
          N_('Create container in a compute resource')
62
      param_group :container, :as => :create
63

  
64
      def create
65
        @container = Service::Containers.new.start_container!(set_wizard_state)
66
        set_container_taxonomies
67
        process_response @container.save
68
      rescue ActiveModel::MassAssignmentSecurity::Error => e
69
        render :json => { :error  => _("Wrong attributes: %s") % e.message },
70
               :status => :unprocessable_entity
71
      end
72

  
73
      api :DELETE, '/containers/:id/', N_('Delete a container')
74
      api :DELETE, '/compute_resources/:compute_resource_id/containers/:id',
75
          N_('Delete container in a compute resource')
76
      param :id, :identifier, :required => true
77
      param :compute_resource_id, :identifier
78

  
79
      def destroy
80
        process_response @container.destroy
81
      end
82

  
83
      api :GET, '/containers/:id/logs', N_('Show container logs')
84
      api :GET, '/compute_resources/:compute_resource_id/containers/:id/logs',
85
          N_('Show logs from a container in a compute resource')
86
      param :id, :identifier, :required => true
87
      param :compute_resource_id, :identifier
88
      param :stdout, :bool
89
      param :stderr, :bool
90
      param :tail,   Fixnum, N_('Number of lines to tail. Default: 100')
91

  
92
      def logs
93
        render :json => { :logs => Docker::Container.get(@container.uuid)
94
          .logs(:stdout => (params[:stdout] || true),
95
                :stderr => (params[:stderr] || false),
96
                :tail   => (params[:tail]   || 100)) }
97
      end
98

  
99
      api :PUT, '/containers/:id/power', N_('Run power operation on a container')
100
      api :PUT, '/compute_resources/:compute_resource_id/containers/:id/power',
101
          N_('Run power operation on a container in a compute resource')
102
      param :id, :identifier, :required => true
103
      param :compute_resource_id, :identifier
104
      param :power_action, String,
105
            :required => true,
106
            :desc     => N_('power action, valid actions are (start), (stop), (status)')
107

  
108
      def power
109
        power_actions = %(start stop status)
110
        if power_actions.include? params[:power_action]
111
          response = if params[:power_action] == 'status'
112
                       { :running => @container.in_fog.ready? }
113
                     else
114
                       { :running => @container.in_fog.send(params[:power_action]) }
115
                     end
116
          render :json => response
117
        else
118
          render :json =>
119
            { :error => _("Unknown method: available power operations are %s") %
120
              power_actions.join(', ') }, :status => :unprocessable_entity
121
        end
122
      end
123

  
124
      private
125

  
126
      def set_wizard_state
127
        wizard_properties = { :preliminary   => [:compute_resource_id],
128
                              :image         => [:registry_id, :repository_name, :tag, :katello],
129
                              :configuration => [:name, :command, :entrypoint, :cpu_set,
130
                                                 :cpu_shares, :memory],
131
                              :environment   => [:tty, :attach_stdin, :attach_stdout,
132
                                                 :attach_stderr] }
133

  
134
        wizard_state = DockerContainerWizardState.create
135
        wizard_properties.each do |step, properties|
136
          property_values = properties.each_with_object({}) do |property, values|
137
            values[:"#{property}"] = params[:container][:"#{property}"]
138
          end
139
          wizard_state.send(:"create_#{step}", property_values)
140
        end
141

  
142
        if params[:container][:environment_variables].present?
143
          wizard_state.environment.environment_variables =
144
            params[:container][:environment_variables]
145
        end
146
        wizard_state.tap(&:save)
147
      end
148

  
149
      def set_container_taxonomies
150
        Taxonomy.enabled_taxonomies.each do |taxonomy|
151
          if params[:container][:"#{taxonomy}"].present?
152
            @container.send(:"#{taxonomy}=", params[:container][:"#{taxonomy}"])
153
          end
154
        end
155
      end
156

  
157
      def action_permission
158
        case params[:action]
159
        when 'logs'
160
          :view
161
        when 'power'
162
          :edit
163
        else
164
          super
165
        end
166
      end
167
    end
168
  end
169
end
170
# end
app/views/api/v2/containers/base.json.rabl
1
object @container
2

  
3
attributes :id, :name, :uuid
app/views/api/v2/containers/index.json.rabl
1
collection @containers
2

  
3
extends 'api/v2/containers/main'
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
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
config/routes.rb
11 11
  end
12 12

  
13 13
  resources :image_search, :only => [] do
14
    get :auto_complete_repository_name, :on => :member
15
    get :auto_complete_image_tag, :on => :member
16
    get :search_repository, :on => :member
14
    member do
15
      get :auto_complete_repository_name
16
      get :auto_complete_image_tag
17
      get :search_repository
18
    end
17 19
  end
18 20

  
19
  resources :registries, :only => [:index, :new, :create, :update, :destroy, :edit]
21
  resources :registries, :except => [:show]
22

  
23
  scope :foreman_docker, :path => '/docker' do
24
    namespace :api, :defaults => { :format => 'json' } do
25
      scope "(:apiv)", :module => :v2, :defaults => { :apiv => 'v2' }, :apiv => /v2/,
26
                       :constraints => ApiConstraints.new(:version => 2) do
27
        resources :containers, :only => [:index, :create, :show, :destroy] do
28
          member do
29
            get :logs
30
            put :power
31
          end
32
        end
33
        resources :compute_resources, :only => [] do
34
          resources :containers, :only => [:index, :create, :show, :destroy] do
35
            member do
36
              get :logs
37
              put :power
38
            end
39
          end
40
        end
41
      end
42
    end
43
  end
20 44
end
lib/foreman_docker/engine.rb
57 57
        end
58 58

  
59 59
        security_block :containers do
60
          permission :view_containers,    :containers         => [:index, :show]
61
          permission :commit_containers,  :containers         => [:commit]
62
          permission :create_containers,  :'containers/steps' => [:show, :update],
63
                                          :containers         => [:new]
64
          permission :destroy_containers, :containers         => [:destroy]
65
          permission :power_compute_resources_vms, :containers => [:power]
60
          permission :view_containers,
61
                     :containers          => [:index, :show],
62
                     :'api/v2/containers' => [:index, :show, :logs]
63
          permission :commit_containers, :containers => [:commit]
64
          permission :create_containers,
65
                     :'containers/steps'  => [:show, :update],
66
                     :containers          => [:new],
67
                     :'api/v2/containers' => [:create, :power]
68
          permission :destroy_containers,
69
                     :containers          => [:destroy],
70
                     :'api/v2/containers' => [:destroy]
71
          permission :power_compute_resources_vms,
72
                     :containers          => [:power],
73
                     :'api/v2/containers' => [:create, :power]
66 74
        end
67 75

  
68 76
        security_block :registries do
......
77 85
                                       :auto_complete_image_tag,
78 86
                                       :search_repository]
79 87
        end
88

  
89
        # apipie API documentation
90
        # Only available in 1.8, otherwise it has to be in the initializer below
91
        if SETTINGS[:version].to_s.include?('develop') ||
92
           Gem::Version.new(SETTINGS[:version]) >= Gem::Version.new('1.8')
93
          apipie_documented_controllers [
94
            "#{ForemanDocker::Engine.root}/app/controllers/api/v2/*.rb"]
95
        end
96
      end
97
    end
98

  
99
    initializer "foreman_docker.apipie" do
100
      # this condition is here for compatibility reason to work with Foreman 1.4.x
101
      # Also need to handle the reverse of the 1.8 method above
102
      unless SETTINGS[:version].to_s.include?('develop') ||
103
             Gem::Version.new(SETTINGS[:version]) >= Gem::Version.new('1.8')
104
        if Apipie.configuration.api_controllers_matcher.is_a?(Array)
105
          Apipie.configuration.api_controllers_matcher <<
106
            "#{ForemanDocker::Engine.root}/app/controllers/api/v2/*.rb"
107
        end
80 108
      end
81 109
    end
82 110

  
test/functionals/api/v2/containers_controller_test.rb
1
require 'test_plugin_helper'
2

  
3
module Api
4
  module V2
5
    class ContainersControllerTest < ActionController::TestCase
6
      test 'index returns a list of all containers' do
7
        get :index, {}, set_session_user
8
        assert_response :success
9
        assert_template 'index'
10
      end
11

  
12
      context 'container operations' do
13
        setup do
14
          @container = FactoryGirl.create(:container, :name => 'foo')
15
        end
16

  
17
        test 'logs returns latest lines of container log' do
18
          fake_container = Struct.new(:logs)
19
          fake_container.expects(:logs).returns('I am a log').twice
20
          Docker::Container.expects(:get).with(@container.uuid).returns(fake_container)
21
          get :logs, :id => @container.id
22
          assert_response :success
23
          assert_equal ActiveSupport::JSON.decode(response.body)['logs'], fake_container.logs
24
        end
25

  
26
        test 'show returns information about container'  do
27
          get :show, :id => @container.id
28
          assert_response :success
29
          assert_equal ActiveSupport::JSON.decode(response.body)['name'], 'foo'
30
        end
31

  
32
        test 'delete removes a container in foreman and in Docker host' do
33
          delete :destroy, :id => @container.id
34
          assert_response :success
35
          assert_equal ActiveSupport::JSON.decode(response.body)['name'], 'foo'
36
        end
37

  
38
        test 'power call turns on/off container in Docker host' do
39
          Fog.mock!
40
          Fog::Compute::Fogdocker::Server.any_instance.expects(:start)
41
          put :power, :id => @container.id, :power_action => 'start'
42
          assert_response :success
43
        end
44

  
45
        test 'power call checks status of container in Docker host' do
46
          Fog.mock!
47
          Fog::Compute::Fogdocker::Server.any_instance.expects(:ready?).returns(false)
48
          put :power, :id => @container.id, :power_action => 'status'
49
          assert_response :success
50
          assert_equal ActiveSupport::JSON.decode(response.body)['running'], false
51
        end
52

  
53
        test 'power call host' do
54
          Fog.mock!
55
          Fog::Compute::Fogdocker::Server.any_instance.expects(:ready?).returns(false)
56
          put :power, :id => @container.id, :power_action => 'status'
57
          assert_response :success
58
          assert_equal ActiveSupport::JSON.decode(response.body)['running'], false
59
        end
60

  
61
        test 'creates a container with correct params' do
62
          Service::Containers.any_instance.expects(:pull_image).returns(true)
63
          Service::Containers.any_instance.expects(:start_container).returns(true)
64
          post :create, :container => { :name => 'foo', :registry_id => 3, :image => 'centos:7' }
65
          assert_response :created
66
        end
67
      end
68
    end
69
  end
70
end
test/integration/container_test.rb
8 8

  
9 9
  context 'available compute resource' do
10 10
    test 'shows containers list if compute resource is available' do
11
      Fog.mock!
11 12
      ComputeResource.any_instance.stubs(:vms).returns([])
12 13
      FactoryGirl.create(:docker_cr)
13 14
      visit containers_path

Also available in: Unified diff