diff --git a/doc/api/environments.md b/doc/api/environments.md index d23351493019a7c3599b5adc3bfd4fa82c87a3d3..87f99bc9034e112ccfdd510284dd55317a5353b0 100644 --- a/doc/api/environments.md +++ b/doc/api/environments.md @@ -7,6 +7,8 @@ type: concepts, howto # Environments API **(FREE)** +> Support for [GitLab CI/CD job token](../ci/jobs/ci_job_token.md) authentication [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414549) in GitLab 16.2. + ## List environments Get all environments for a given project. diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md index a67cd2de4e8bf87461bf85a99147885060d5f4ba..c2fe3071b5285f42edb9f7832197f48192345146 100644 --- a/doc/ci/jobs/ci_job_token.md +++ b/doc/ci/jobs/ci_job_token.md @@ -25,6 +25,7 @@ You can use a GitLab CI/CD job token to authenticate with specific API endpoints - [Releases](../../api/releases/index.md) and [Release links](../../api/releases/links.md). - [Terraform plan](../../user/infrastructure/index.md). - [Deployments](../../api/deployments.md). +- [Environments](../../api/environments.md). The token has the same permissions to access the API as the user that caused the job to run. A user can cause a job to run by taking action like pushing a commit, diff --git a/lib/api/environments.rb b/lib/api/environments.rb index bb261079d2a08fcdde6ea1d738534bff8f66fba8..b94391359ed75f98743fb5c4512f0ae24770d3ce 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -38,6 +38,7 @@ class Environments < ::API::Base desc: 'List all environments that match a specific state. Accepted values: `available`, `stopping`, or `stopped`. If no state value given, returns all environments' mutually_exclusive :name, :search, message: 'cannot be used together' end + route_setting :authentication, job_token_allowed: true get ':id/environments' do authorize! :read_environment, user_project @@ -66,6 +67,7 @@ class Environments < ::API::Base optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true } optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`' end + route_setting :authentication, job_token_allowed: true post ':id/environments' do authorize! :create_environment, user_project @@ -94,6 +96,7 @@ class Environments < ::API::Base optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true } optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`' end + route_setting :authentication, job_token_allowed: true put ':id/environments/:environment_id' do authorize! :update_environment, user_project @@ -126,6 +129,7 @@ class Environments < ::API::Base optional :limit, type: Integer, desc: "Maximum number of environments to delete. Defaults to 100", default: 100, values: 1..1000 optional :dry_run, type: Boolean, desc: "Defaults to true for safety reasons. It performs a dry run where no actual deletion will be performed. Set to false to actually delete the environment", default: true end + route_setting :authentication, job_token_allowed: true delete ":id/environments/review_apps" do authorize! :read_environment, user_project @@ -156,6 +160,7 @@ class Environments < ::API::Base params do requires :environment_id, type: Integer, desc: 'The ID of the environment' end + route_setting :authentication, job_token_allowed: true delete ':id/environments/:environment_id' do authorize! :read_environment, user_project @@ -178,6 +183,7 @@ class Environments < ::API::Base requires :environment_id, type: Integer, desc: 'The ID of the environment' optional :force, type: Boolean, default: false, desc: 'Force environment to stop without executing `on_stop` actions' end + route_setting :authentication, job_token_allowed: true post ':id/environments/:environment_id/stop' do authorize! :read_environment, user_project @@ -202,6 +208,7 @@ class Environments < ::API::Base type: DateTime, desc: 'Stop all environments that were last modified or deployed to before this date.' end + route_setting :authentication, job_token_allowed: true post ':id/environments/stop_stale' do authorize! :stop_environment, user_project @@ -229,6 +236,7 @@ class Environments < ::API::Base params do requires :environment_id, type: Integer, desc: 'The ID of the environment' end + route_setting :authentication, job_token_allowed: true get ':id/environments/:environment_id' do authorize! :read_environment, user_project diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 9a435b3bce952f32c3c57f92cf87180bc5e24b67..498e030da0bec9f2b60b462847eff9a0d2b4faac 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -31,6 +31,14 @@ expect(json_response.first).not_to have_key('last_deployment') end + it 'returns 200 HTTP status when using JOB-TOKEN auth' do + job = create(:ci_build, :running, project: project, user: user) + + get api("/projects/#{project.id}/environments"), params: { job_token: job.token } + + expect(response).to have_gitlab_http_status(:ok) + end + context 'when filtering' do let_it_be(:stopped_environment) { create(:environment, :stopped, project: project) } @@ -132,6 +140,14 @@ expect(json_response['external']).to be nil end + it 'returns 200 HTTP status when using JOB-TOKEN auth' do + job = create(:ci_build, :running, project: project, user: user) + + post api("/projects/#{project.id}/environments"), params: { name: "mepmep", job_token: job.token } + + expect(response).to have_gitlab_http_status(:created) + end + it 'requires name to be passed' do post api("/projects/#{project.id}/environments", user), params: { external_url: 'test.gitlab.com' } @@ -173,6 +189,15 @@ expect(response).to have_gitlab_http_status(:ok) end + it 'returns 200 HTTP status when using JOB-TOKEN auth' do + job = create(:ci_build, :running, project: project, user: user) + + post api("/projects/#{project.id}/environments/stop_stale"), + params: { before: 1.week.ago.to_date.to_s, job_token: job.token } + + expect(response).to have_gitlab_http_status(:ok) + end + it 'returns a 400 for bad input date' do post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.day.ago.to_date.to_s } @@ -229,6 +254,15 @@ expect(json_response['tier']).to eq('production') end + it 'returns 200 HTTP status when using JOB-TOKEN auth' do + job = create(:ci_build, :running, project: project, user: user) + + put api("/projects/#{project.id}/environments/#{environment.id}"), + params: { tier: 'production', job_token: job.token } + + expect(response).to have_gitlab_http_status(:ok) + end + it "won't allow slug to be changed" do slug = environment.slug api_url = api("/projects/#{project.id}/environments/#{environment.id}", user) @@ -261,6 +295,17 @@ expect(response).to have_gitlab_http_status(:no_content) end + it 'returns 204 HTTP status when using JOB-TOKEN auth' do + environment.stop + + job = create(:ci_build, :running, project: project, user: user) + + delete api("/projects/#{project.id}/environments/#{environment.id}"), + params: { job_token: job.token } + + expect(response).to have_gitlab_http_status(:no_content) + end + it 'returns a 404 for non existing id' do delete api("/projects/#{project.id}/environments/#{non_existing_record_id}", user) @@ -291,17 +336,23 @@ context 'with a stoppable environment' do before do environment.update!(state: :available) - - post api("/projects/#{project.id}/environments/#{environment.id}/stop", user) end it 'returns a 200' do + post api("/projects/#{project.id}/environments/#{environment.id}/stop", user) + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/environment') + expect(environment.reload).to be_stopped end - it 'actually stops the environment' do - expect(environment.reload).to be_stopped + it 'returns 200 HTTP status when using JOB-TOKEN auth' do + job = create(:ci_build, :running, project: project, user: user) + + post api("/projects/#{project.id}/environments/#{environment.id}/stop"), + params: { job_token: job.token } + + expect(response).to have_gitlab_http_status(:ok) end end @@ -333,6 +384,15 @@ expect(response).to match_response_schema('public_api/v4/environment') expect(json_response['last_deployment']).to be_present end + + it 'returns 200 HTTP status when using JOB-TOKEN auth' do + job = create(:ci_build, :running, project: project, user: user) + + get api("/projects/#{project.id}/environments/#{environment.id}"), + params: { job_token: job.token } + + expect(response).to have_gitlab_http_status(:ok) + end end context 'as non member' do