Commit 743c531e authored by James Fargher's avatar James Fargher

Merge branch '196784-add-container-expiration-policy-to-graphql-project' into 'master'

Add container expiration policy to GraphQL project

See merge request gitlab-org/gitlab!32100
parents 7e369ce4 a809b015
# frozen_string_literal: true
module Types
class ContainerExpirationPolicyCadenceEnum < BaseEnum
OPTIONS_MAPPING = {
'1d': 'EVERY_DAY',
'7d': 'EVERY_WEEK',
'14d': 'EVERY_TWO_WEEKS',
'1month': 'EVERY_MONTH',
'3month': 'EVERY_THREE_MONTHS'
}.freeze
::ContainerExpirationPolicy.cadence_options.each do |option, description|
value OPTIONS_MAPPING[option], description, value: option.to_s
end
end
end
# frozen_string_literal: true
module Types
class ContainerExpirationPolicyKeepEnum < BaseEnum
OPTIONS_MAPPING = {
1 => 'ONE_TAG',
5 => 'FIVE_TAGS',
10 => 'TEN_TAGS',
25 => 'TWENTY_FIVE_TAGS',
50 => 'FIFTY_TAGS',
100 => 'ONE_HUNDRED_TAGS'
}.freeze
::ContainerExpirationPolicy.keep_n_options.each do |option, description|
value OPTIONS_MAPPING[option], description, value: option
end
end
end
# frozen_string_literal: true
module Types
class ContainerExpirationPolicyOlderThanEnum < BaseEnum
OPTIONS_MAPPING = {
'7d': 'SEVEN_DAYS',
'14d': 'FOURTEEN_DAYS',
'30d': 'THIRTY_DAYS',
'90d': 'NINETY_DAYS'
}.freeze
::ContainerExpirationPolicy.older_than_options.each do |option, description|
value OPTIONS_MAPPING[option], description, value: option.to_s
end
end
end
# frozen_string_literal: true
module Types
class ContainerExpirationPolicyType < BaseObject
graphql_name 'ContainerExpirationPolicy'
description 'A tag expiration policy designed to keep only the images that matter most'
authorize :destroy_container_image
field :created_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was created'
field :updated_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was updated'
field :enabled, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates if this container expiration policy is enabled'
field :older_than, Types::ContainerExpirationPolicyOlderThanEnum, null: true, description: 'Tags older that this will expire'
field :cadence, Types::ContainerExpirationPolicyCadenceEnum, null: false, description: 'This container expiration policy schedule'
field :keep_n, Types::ContainerExpirationPolicyKeepEnum, null: true, description: 'Number of tags to retain'
field :name_regex, GraphQL::STRING_TYPE, null: true, description: 'Tags with names matching this regex pattern will expire'
field :name_regex_keep, GraphQL::STRING_TYPE, null: true, description: 'Tags with names matching this regex pattern will be preserved'
field :next_run_at, Types::TimeType, null: true, description: 'Next time that this container expiration policy will get executed'
end
end
...@@ -237,6 +237,11 @@ module Types ...@@ -237,6 +237,11 @@ module Types
description: 'A single release of the project', description: 'A single release of the project',
resolver: Resolvers::ReleasesResolver.single, resolver: Resolvers::ReleasesResolver.single,
feature_flag: :graphql_release_data feature_flag: :graphql_release_data
field :container_expiration_policy,
Types::ContainerExpirationPolicyType,
null: true,
description: 'The container expiration policy of the project'
end end
end end
......
# frozen_string_literal: true
class ContainerExpirationPolicyPolicy < BasePolicy
delegate { @subject.project }
end
---
title: Add the container expiration policy attribute to the project GraphQL type
merge_request: 32100
author:
type: added
...@@ -935,6 +935,137 @@ type Commit { ...@@ -935,6 +935,137 @@ type Commit {
webUrl: String! webUrl: String!
} }
"""
A tag expiration policy designed to keep only the images that matter most
"""
type ContainerExpirationPolicy {
"""
This container expiration policy schedule
"""
cadence: ContainerExpirationPolicyCadenceEnum!
"""
Timestamp of when the container expiration policy was created
"""
createdAt: Time!
"""
Indicates if this container expiration policy is enabled
"""
enabled: Boolean!
"""
Number of tags to retain
"""
keepN: ContainerExpirationPolicyKeepEnum
"""
Tags with names matching this regex pattern will expire
"""
nameRegex: String
"""
Tags with names matching this regex pattern will be preserved
"""
nameRegexKeep: String
"""
Next time that this container expiration policy will get executed
"""
nextRunAt: Time
"""
Tags older that this will expire
"""
olderThan: ContainerExpirationPolicyOlderThanEnum
"""
Timestamp of when the container expiration policy was updated
"""
updatedAt: Time!
}
enum ContainerExpirationPolicyCadenceEnum {
"""
Every day
"""
EVERY_DAY
"""
Every month
"""
EVERY_MONTH
"""
Every three months
"""
EVERY_THREE_MONTHS
"""
Every two weeks
"""
EVERY_TWO_WEEKS
"""
Every week
"""
EVERY_WEEK
}
enum ContainerExpirationPolicyKeepEnum {
"""
50 tags per image name
"""
FIFTY_TAGS
"""
5 tags per image name
"""
FIVE_TAGS
"""
100 tags per image name
"""
ONE_HUNDRED_TAGS
"""
1 tag per image name
"""
ONE_TAG
"""
10 tags per image name
"""
TEN_TAGS
"""
25 tags per image name
"""
TWENTY_FIVE_TAGS
}
enum ContainerExpirationPolicyOlderThanEnum {
"""
14 days until tags are automatically removed
"""
FOURTEEN_DAYS
"""
90 days until tags are automatically removed
"""
NINETY_DAYS
"""
7 days until tags are automatically removed
"""
SEVEN_DAYS
"""
30 days until tags are automatically removed
"""
THIRTY_DAYS
}
""" """
Autogenerated input type of CreateAlertIssue Autogenerated input type of CreateAlertIssue
""" """
...@@ -7707,6 +7838,11 @@ type Project { ...@@ -7707,6 +7838,11 @@ type Project {
last: Int last: Int
): BoardConnection ): BoardConnection
"""
The container expiration policy of the project
"""
containerExpirationPolicy: ContainerExpirationPolicy
""" """
Indicates if the project stores Docker container images in a container registry Indicates if the project stores Docker container images in a container registry
""" """
......
...@@ -2515,6 +2515,284 @@ ...@@ -2515,6 +2515,284 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "ContainerExpirationPolicy",
"description": "A tag expiration policy designed to keep only the images that matter most",
"fields": [
{
"name": "cadence",
"description": "This container expiration policy schedule",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "ContainerExpirationPolicyCadenceEnum",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createdAt",
"description": "Timestamp of when the container expiration policy was created",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "enabled",
"description": "Indicates if this container expiration policy is enabled",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "keepN",
"description": "Number of tags to retain",
"args": [
],
"type": {
"kind": "ENUM",
"name": "ContainerExpirationPolicyKeepEnum",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nameRegex",
"description": "Tags with names matching this regex pattern will expire",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nameRegexKeep",
"description": "Tags with names matching this regex pattern will be preserved",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nextRunAt",
"description": "Next time that this container expiration policy will get executed",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "olderThan",
"description": "Tags older that this will expire",
"args": [
],
"type": {
"kind": "ENUM",
"name": "ContainerExpirationPolicyOlderThanEnum",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp of when the container expiration policy was updated",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "ContainerExpirationPolicyCadenceEnum",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "EVERY_DAY",
"description": "Every day",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EVERY_WEEK",
"description": "Every week",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EVERY_TWO_WEEKS",
"description": "Every two weeks",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EVERY_MONTH",
"description": "Every month",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EVERY_THREE_MONTHS",
"description": "Every three months",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "ContainerExpirationPolicyKeepEnum",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "ONE_TAG",
"description": "1 tag per image name",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FIVE_TAGS",
"description": "5 tags per image name",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "TEN_TAGS",
"description": "10 tags per image name",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "TWENTY_FIVE_TAGS",
"description": "25 tags per image name",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FIFTY_TAGS",
"description": "50 tags per image name",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ONE_HUNDRED_TAGS",
"description": "100 tags per image name",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "ContainerExpirationPolicyOlderThanEnum",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "SEVEN_DAYS",
"description": "7 days until tags are automatically removed",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FOURTEEN_DAYS",
"description": "14 days until tags are automatically removed",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "THIRTY_DAYS",
"description": "30 days until tags are automatically removed",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NINETY_DAYS",
"description": "90 days until tags are automatically removed",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "CreateAlertIssueInput", "name": "CreateAlertIssueInput",
...@@ -23003,6 +23281,20 @@ ...@@ -23003,6 +23281,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "containerExpirationPolicy",
"description": "The container expiration policy of the project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "ContainerExpirationPolicy",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "containerRegistryEnabled", "name": "containerRegistryEnabled",
"description": "Indicates if the project stores Docker container images in a container registry", "description": "Indicates if the project stores Docker container images in a container registry",
...@@ -177,6 +177,22 @@ Autogenerated return type of BoardListUpdateLimitMetrics ...@@ -177,6 +177,22 @@ Autogenerated return type of BoardListUpdateLimitMetrics
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` | | `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `webUrl` | String! | Web URL of the commit | | `webUrl` | String! | Web URL of the commit |
## ContainerExpirationPolicy
A tag expiration policy designed to keep only the images that matter most
| Name | Type | Description |
| --- | ---- | ---------- |
| `cadence` | ContainerExpirationPolicyCadenceEnum! | This container expiration policy schedule |
| `createdAt` | Time! | Timestamp of when the container expiration policy was created |
| `enabled` | Boolean! | Indicates if this container expiration policy is enabled |
| `keepN` | ContainerExpirationPolicyKeepEnum | Number of tags to retain |
| `nameRegex` | String | Tags with names matching this regex pattern will expire |
| `nameRegexKeep` | String | Tags with names matching this regex pattern will be preserved |
| `nextRunAt` | Time | Next time that this container expiration policy will get executed |
| `olderThan` | ContainerExpirationPolicyOlderThanEnum | Tags older that this will expire |
| `updatedAt` | Time! | Timestamp of when the container expiration policy was updated |
## CreateAlertIssuePayload ## CreateAlertIssuePayload
Autogenerated return type of CreateAlertIssue Autogenerated return type of CreateAlertIssue
...@@ -1161,6 +1177,7 @@ Information about pagination in a connection. ...@@ -1161,6 +1177,7 @@ Information about pagination in a connection.
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically | | `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically |
| `avatarUrl` | String | URL to avatar image file of the project | | `avatarUrl` | String | URL to avatar image file of the project |
| `board` | Board | A single board of the project | | `board` | Board | A single board of the project |
| `containerExpirationPolicy` | ContainerExpirationPolicy | The container expiration policy of the project |
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry | | `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
| `createdAt` | Time | Timestamp of the project creation | | `createdAt` | Time | Timestamp of the project creation |
| `description` | String | Short description of the project | | `description` | String | Short description of the project |
......
# frozen_string_literal: true
require 'spec_helper'
describe ContainerExpirationPolicyPolicy do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
subject { described_class.new(user, project.container_expiration_policy) }
where(:user_type, :allowed_to_destroy_container_image) do
:anonymous | false
:guest | false
:developer | true
end
with_them do
context "for user type #{params[:user_type]}" do
before do
project.public_send("add_#{user_type}", user) unless user_type == :anonymous
end
if params[:allowed_to_destroy_container_image]
it { is_expected.to be_allowed(:destroy_container_image) }
else
it { is_expected.not_to be_allowed(:destroy_container_image) }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['ContainerExpirationPolicyCadenceEnum'] do
let_it_be(:expected_values) { %w[EVERY_DAY EVERY_WEEK EVERY_TWO_WEEKS EVERY_MONTH EVERY_THREE_MONTHS] }
it_behaves_like 'exposing container expiration policy option', :cadence
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['ContainerExpirationPolicyKeepEnum'] do
let_it_be(:expected_values) { %w[ONE_TAG FIVE_TAGS TEN_TAGS TWENTY_FIVE_TAGS FIFTY_TAGS ONE_HUNDRED_TAGS] }
it_behaves_like 'exposing container expiration policy option', :keep_n
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['ContainerExpirationPolicyOlderThanEnum'] do
let_it_be(:expected_values) { %w[SEVEN_DAYS FOURTEEN_DAYS THIRTY_DAYS NINETY_DAYS] }
it_behaves_like 'exposing container expiration policy option', :older_than
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['ContainerExpirationPolicy'] do
specify { expect(described_class.graphql_name).to eq('ContainerExpirationPolicy') }
specify { expect(described_class.description).to eq('A tag expiration policy designed to keep only the images that matter most') }
specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
describe 'older_than field' do
subject { described_class.fields['olderThan'] }
it 'returns older_than enum' do
is_expected.to have_graphql_type(Types::ContainerExpirationPolicyOlderThanEnum)
end
end
describe 'keep n field' do
subject { described_class.fields['keepN'] }
it 'returns keep enum' do
is_expected.to have_graphql_type(Types::ContainerExpirationPolicyKeepEnum)
end
end
end
...@@ -26,6 +26,7 @@ describe GitlabSchema.types['Project'] do ...@@ -26,6 +26,7 @@ describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
...@@ -125,4 +126,10 @@ describe GitlabSchema.types['Project'] do ...@@ -125,4 +126,10 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ReleaseType.connection_type) } it { is_expected.to have_graphql_type(Types::ReleaseType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::ReleasesResolver) } it { is_expected.to have_graphql_resolver(Resolvers::ReleasesResolver) }
end end
describe 'container expiration policy field' do
subject { described_class.fields['containerExpirationPolicy'] }
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe 'getting a repository in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { project.owner }
let_it_be(:container_expiration_policy) { project.container_expiration_policy }
let(:fields) do
<<~QUERY
#{all_graphql_fields_for('container_expiration_policy'.classify)}
QUERY
end
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('containerExpirationPolicy', {}, fields)
)
end
before do
stub_config(registry: { enabled: true })
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
end
# frozen_string_literal: true
RSpec.shared_examples 'exposing container expiration policy option' do |model_option|
it 'exposes all options' do
expect(described_class.values.keys).to contain_exactly(*expected_values)
end
it 'uses all possible options from model' do
all_options = ContainerExpirationPolicy.public_send("#{model_option}_options").keys
expect(described_class::OPTIONS_MAPPING.keys).to contain_exactly(*all_options)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment