Read about our upcoming Code of Conduct on this issue

environments_controller.rb 7.34 KB
Newer Older
1
2
# frozen_string_literal: true

Kamil Trzcinski's avatar
Kamil Trzcinski committed
3
class Projects::EnvironmentsController < Projects::ApplicationController
4
5
  include MetricsDashboard

Kamil Trzcinski's avatar
Kamil Trzcinski committed
6
  layout 'project'
7
  before_action :authorize_read_environment!
Kamil Trzcinski's avatar
Kamil Trzcinski committed
8
  before_action :authorize_create_environment!, only: [:new, :create]
9
  before_action :authorize_stop_environment!, only: [:stop]
10
  before_action :authorize_update_environment!, only: [:edit, :update, :cancel_auto_stop]
11
  before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
12
  before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :cancel_auto_stop]
13
  before_action :verify_api_request!, only: :terminal_websocket_authorize
14
  before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
15
  before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
16
    push_frontend_feature_flag(:prometheus_computed_alerts)
17
  end
18
  after_action :expire_etag_cache, only: [:cancel_auto_stop]
Kamil Trzcinski's avatar
Kamil Trzcinski committed
19
20

  def index
21
22
    @environments = project.environments
      .with_state(params[:scope] || :available)
23

24
25
26
    respond_to do |format|
      format.html
      format.json do
27
28
        Gitlab::PollingInterval.set_header(response, interval: 3_000)

29
        render json: {
30
          environments: serialize_environments(request, response, params[:nested]),
31
32
33
          available_count: project.environments.available.count,
          stopped_count: project.environments.stopped.count
        }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
34
      end
35
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
36
37
  end

38
  # Returns all environments for a given folder
39
  # rubocop: disable CodeReuse/ActiveRecord
40
  def folder
41
42
    folder_environments = project.environments.where(environment_type: params[:id])
    @environments = folder_environments.with_state(params[:scope] || :available)
43
      .order(:name)
44
    @folder = params[:id]
45
46
47
48
49

    respond_to do |format|
      format.html
      format.json do
        render json: {
50
          environments: serialize_environments(request, response),
51
52
          available_count: folder_environments.available.count,
          stopped_count: folder_environments.stopped.count
53
54
55
56
        }
      end
    end
  end
57
  # rubocop: enable CodeReuse/ActiveRecord
58

59
  # rubocop: disable CodeReuse/ActiveRecord
Kamil Trzcinski's avatar
Kamil Trzcinski committed
60
  def show
Kamil Trzcinski's avatar
Kamil Trzcinski committed
61
    @deployments = environment.deployments.order(id: :desc).page(params[:page])
Kamil Trzcinski's avatar
Kamil Trzcinski committed
62
  end
63
  # rubocop: enable CodeReuse/ActiveRecord
Kamil Trzcinski's avatar
Kamil Trzcinski committed
64
65
66
67
68

  def new
    @environment = project.environments.new
  end

69
70
71
  def edit
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
72
  def create
73
    @environment = project.environments.create(environment_params)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
74
75

    if @environment.persisted?
76
      redirect_to project_environment_path(project, @environment)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
77
    else
78
79
80
81
82
83
      render :new
    end
  end

  def update
    if @environment.update(environment_params)
84
      redirect_to project_environment_path(project, @environment)
85
86
    else
      render :edit
Kamil Trzcinski's avatar
Kamil Trzcinski committed
87
88
89
    end
  end

90
  def stop
91
    return render_404 unless @environment.available?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
92

Kamil Trzcinski's avatar
Kamil Trzcinski committed
93
94
    stop_action = @environment.stop_with_action!(current_user)

Fatih Acet's avatar
Fatih Acet committed
95
96
97
98
    action_or_env_url =
      if stop_action
        polymorphic_url([project.namespace.becomes(Namespace), project, stop_action])
      else
99
        project_environment_url(project, @environment)
Fatih Acet's avatar
Fatih Acet committed
100
101
102
103
104
      end

    respond_to do |format|
      format.html { redirect_to action_or_env_url }
      format.json { render json: { redirect_url: action_or_env_url } }
105
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
106
107
  end

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
  def cancel_auto_stop
    result = Environments::ResetAutoStopService.new(project, current_user)
      .execute(environment)

    if result[:status] == :success
      respond_to do |format|
        message = _('Auto stop successfully canceled.')

        format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
        format.json { render json: { message: message }, status: :ok }
      end
    else
      respond_to do |format|
        message = result[:message]

        format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: message }) }
        format.json { render json: { message: message }, status: :unprocessable_entity }
      end
    end
  end

129
130
131
132
133
134
135
136
137
138
139
  def terminal
    # Currently, this acts as a hint to load the terminal details into the cache
    # if they aren't there already. In the future, users will need these details
    # to choose between terminals to connect to.
    @terminals = environment.terminals
  end

  # GET .../terminal.ws : implemented in gitlab-workhorse
  def terminal_websocket_authorize
    # Just return the first terminal for now. If the list is in the process of
    # being looked up, this may result in a 404 response, so the frontend
140
    # should retry those errors
141
142
143
    terminal = environment.terminals.try(:first)
    if terminal
      set_workhorse_internal_api_content_type
144
      render json: Gitlab::Workhorse.channel_websocket(terminal)
145
    else
146
      render html: 'Not found', status: :not_found
147
148
149
    end
  end

150
  def metrics_redirect
151
    environment = project.default_environment
152

153
154
155
    if environment
      redirect_to environment_metrics_path(environment)
    else
156
      render :empty_metrics
157
    end
158
159
  end

160
161
162
163
  def metrics
    respond_to do |format|
      format.html
      format.json do
164
165
166
167
        # Currently, this acts as a hint to load the metrics details into the cache
        # if they aren't there already
        @metrics = environment.metrics || {}

168
169
170
171
172
        render json: @metrics, status: @metrics.any? ? :ok : :no_content
      end
    end
  end

173
  def additional_metrics
174
175
    respond_to do |format|
      format.json do
176
        additional_metrics = environment.additional_metrics(*metrics_params) || {}
177

178
179
180
        render json: additional_metrics, status: additional_metrics.any? ? :ok : :no_content
      end
    end
181
182
  end

183
184
185
186
187
188
189
190
191
192
  def search
    respond_to do |format|
      format.json do
        environment_names = search_environment_names

        render json: environment_names, status: environment_names.any? ? :ok : :no_content
      end
    end
  end

193
194
  private

195
196
197
198
  def verify_api_request!
    Gitlab::Workhorse.verify_api_request!(request.headers)
  end

199
200
201
202
203
204
205
  def expire_etag_cache
    # this forces to reload json content
    Gitlab::EtagCaching::Store.new.tap do |store|
      store.touch(project_environments_path(project, format: :json))
    end
  end

206
207
  def environment_params
    params.require(:environment).permit(:name, :external_url)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
208
209
  end

210
  def environment
Kamil Trzcinski's avatar
Kamil Trzcinski committed
211
    @environment ||= project.environments.find(params[:id])
Kamil Trzcinski's avatar
Kamil Trzcinski committed
212
  end
213

214
  def metrics_params
215
    params.require([:start, :end])
216
217
  end

218
219
  def metrics_dashboard_params
    params
220
      .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment)
221
      .merge(dashboard_path: params[:dashboard], environment: environment)
222
223
  end

224
225
  def include_all_dashboards?
    !params[:embedded]
226
227
  end

228
229
230
231
232
233
  def search_environment_names
    return [] unless params[:query]

    project.environments.for_name_like(params[:query]).pluck_names
  end

234
  def serialize_environments(request, response, nested = false)
syasonik's avatar
syasonik committed
235
    EnvironmentSerializer
236
      .new(project: @project, current_user: @current_user)
syasonik's avatar
syasonik committed
237
      .tap { |serializer| serializer.within_folders if nested }
238
      .with_pagination(request, response)
syasonik's avatar
syasonik committed
239
      .represent(@environments)
240
241
  end

242
243
244
  def authorize_stop_environment!
    access_denied! unless can?(current_user, :stop_environment, environment)
  end
245
246
247
248

  def authorize_update_environment!
    access_denied! unless can?(current_user, :update_environment, environment)
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
249
end
250
251

Projects::EnvironmentsController.prepend_if_ee('EE::Projects::EnvironmentsController')