Commit 5b3152a0 authored by Tiger's avatar Tiger

Add latest version field to Terraform state GraphQL type

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45848
parent 480aed65
...@@ -27,6 +27,11 @@ module Types ...@@ -27,6 +27,11 @@ module Types
null: true, null: true,
description: 'Timestamp the Terraform state was locked' description: 'Timestamp the Terraform state was locked'
field :latest_version, Types::Terraform::StateVersionType,
complexity: 3,
null: true,
description: 'The latest version of the Terraform state'
field :created_at, Types::TimeType, field :created_at, Types::TimeType,
null: false, null: false,
description: 'Timestamp the Terraform state was created' description: 'Timestamp the Terraform state was created'
......
# frozen_string_literal: true
module Types
module Terraform
class StateVersionType < BaseObject
graphql_name 'TerraformStateVersion'
authorize :read_terraform_state
field :id, GraphQL::ID_TYPE,
null: false,
description: 'ID of the Terraform state version'
field :created_by_user, Types::UserType,
null: true,
authorize: :read_user,
description: 'The user that created this version',
resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, version.created_by_user_id).find }
field :created_at, Types::TimeType,
null: false,
description: 'Timestamp the version was created'
field :updated_at, Types::TimeType,
null: false,
description: 'Timestamp the version was updated'
end
end
end
...@@ -17,8 +17,15 @@ module Terraform ...@@ -17,8 +17,15 @@ module Terraform
belongs_to :project belongs_to :project
belongs_to :locked_by_user, class_name: 'User' belongs_to :locked_by_user, class_name: 'User'
has_many :versions, class_name: 'Terraform::StateVersion', foreign_key: :terraform_state_id has_many :versions,
has_one :latest_version, -> { ordered_by_version_desc }, class_name: 'Terraform::StateVersion', foreign_key: :terraform_state_id class_name: 'Terraform::StateVersion',
foreign_key: :terraform_state_id,
inverse_of: :terraform_state
has_one :latest_version, -> { ordered_by_version_desc },
class_name: 'Terraform::StateVersion',
foreign_key: :terraform_state_id,
inverse_of: :terraform_state
scope :versioning_not_enabled, -> { where(versioning_enabled: false) } scope :versioning_not_enabled, -> { where(versioning_enabled: false) }
scope :ordered_by_name, -> { order(:name) } scope :ordered_by_name, -> { order(:name) }
......
# frozen_string_literal: true
module Terraform
class StateVersionPolicy < BasePolicy
alias_method :terraform_state_version, :subject
delegate { terraform_state_version.terraform_state }
end
end
---
title: Add latest version field to Terraform state GraphQL type
merge_request: 45848
author:
type: added
...@@ -18993,6 +18993,11 @@ type TerraformState { ...@@ -18993,6 +18993,11 @@ type TerraformState {
""" """
id: ID! id: ID!
"""
The latest version of the Terraform state
"""
latestVersion: TerraformStateVersion
""" """
Timestamp the Terraform state was locked Timestamp the Terraform state was locked
""" """
...@@ -19149,6 +19154,28 @@ type TerraformStateUnlockPayload { ...@@ -19149,6 +19154,28 @@ type TerraformStateUnlockPayload {
errors: [String!]! errors: [String!]!
} }
type TerraformStateVersion {
"""
Timestamp the version was created
"""
createdAt: Time!
"""
The user that created this version
"""
createdByUser: User
"""
ID of the Terraform state version
"""
id: ID!
"""
Timestamp the version was updated
"""
updatedAt: Time!
}
""" """
Represents the Geo sync and verification state of a terraform state version Represents the Geo sync and verification state of a terraform state version
""" """
......
...@@ -55096,6 +55096,20 @@ ...@@ -55096,6 +55096,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "latestVersion",
"description": "The latest version of the Terraform state",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "TerraformStateVersion",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "lockedAt", "name": "lockedAt",
"description": "Timestamp the Terraform state was locked", "description": "Timestamp the Terraform state was locked",
...@@ -55572,6 +55586,87 @@ ...@@ -55572,6 +55586,87 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "TerraformStateVersion",
"description": null,
"fields": [
{
"name": "createdAt",
"description": "Timestamp the version was created",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createdByUser",
"description": "The user that created this version",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the Terraform state version",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp the version 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": "OBJECT", "kind": "OBJECT",
"name": "TerraformStateVersionRegistry", "name": "TerraformStateVersionRegistry",
...@@ -2635,6 +2635,7 @@ Completion status of tasks. ...@@ -2635,6 +2635,7 @@ Completion status of tasks.
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `createdAt` | Time! | Timestamp the Terraform state was created | | `createdAt` | Time! | Timestamp the Terraform state was created |
| `id` | ID! | ID of the Terraform state | | `id` | ID! | ID of the Terraform state |
| `latestVersion` | TerraformStateVersion | The latest version of the Terraform state |
| `lockedAt` | Time | Timestamp the Terraform state was locked | | `lockedAt` | Time | Timestamp the Terraform state was locked |
| `lockedByUser` | User | The user currently holding a lock on the Terraform state | | `lockedByUser` | User | The user currently holding a lock on the Terraform state |
| `name` | String! | Name of the Terraform state | | `name` | String! | Name of the Terraform state |
...@@ -2667,6 +2668,15 @@ Autogenerated return type of TerraformStateUnlock. ...@@ -2667,6 +2668,15 @@ Autogenerated return type of TerraformStateUnlock.
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
### TerraformStateVersion
| Field | Type | Description |
| ----- | ---- | ----------- |
| `createdAt` | Time! | Timestamp the version was created |
| `createdByUser` | User | The user that created this version |
| `id` | ID! | ID of the Terraform state version |
| `updatedAt` | Time! | Timestamp the version was updated |
### TerraformStateVersionRegistry ### TerraformStateVersionRegistry
Represents the Geo sync and verification state of a terraform state version. Represents the Geo sync and verification state of a terraform state version.
......
...@@ -19,7 +19,7 @@ FactoryBot.define do ...@@ -19,7 +19,7 @@ FactoryBot.define do
trait :with_version do trait :with_version do
after(:create) do |state| after(:create) do |state|
create(:terraform_state_version, :with_file, terraform_state: state) create(:terraform_state_version, terraform_state: state)
end end
end end
......
...@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['TerraformState'] do ...@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['TerraformState'] do
it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) } it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
describe 'fields' do describe 'fields' do
let(:fields) { %i[id name locked_by_user locked_at created_at updated_at] } let(:fields) { %i[id name locked_by_user locked_at latest_version created_at updated_at] }
it { expect(described_class).to have_graphql_fields(fields) } it { expect(described_class).to have_graphql_fields(fields) }
...@@ -17,5 +17,8 @@ RSpec.describe GitlabSchema.types['TerraformState'] do ...@@ -17,5 +17,8 @@ RSpec.describe GitlabSchema.types['TerraformState'] do
it { expect(described_class.fields['lockedAt'].type).not_to be_non_null } it { expect(described_class.fields['lockedAt'].type).not_to be_non_null }
it { expect(described_class.fields['createdAt'].type).to be_non_null } it { expect(described_class.fields['createdAt'].type).to be_non_null }
it { expect(described_class.fields['updatedAt'].type).to be_non_null } it { expect(described_class.fields['updatedAt'].type).to be_non_null }
it { expect(described_class.fields['latestVersion'].type).not_to be_non_null }
it { expect(described_class.fields['latestVersion'].complexity).to eq(3) }
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['TerraformStateVersion'] do
it { expect(described_class.graphql_name).to eq('TerraformStateVersion') }
it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
describe 'fields' do
let(:fields) { %i[id created_by_user created_at updated_at] }
it { expect(described_class).to have_graphql_fields(fields) }
it { expect(described_class.fields['id'].type).to be_non_null }
it { expect(described_class.fields['createdByUser'].type).not_to be_non_null }
it { expect(described_class.fields['createdAt'].type).to be_non_null }
it { expect(described_class.fields['updatedAt'].type).to be_non_null }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Terraform::StateVersionPolicy do
let_it_be(:project) { create(:project) }
let_it_be(:terraform_state) { create(:terraform_state, :with_version, project: project)}
subject { described_class.new(user, terraform_state.latest_version) }
describe 'rules' do
context 'no access' do
let(:user) { create(:user) }
it { is_expected.to be_disallowed(:read_terraform_state) }
it { is_expected.to be_disallowed(:admin_terraform_state) }
end
context 'developer' do
let(:user) { create(:user, developer_projects: [project]) }
it { is_expected.to be_allowed(:read_terraform_state) }
it { is_expected.to be_disallowed(:admin_terraform_state) }
end
context 'maintainer' do
let(:user) { create(:user, maintainer_projects: [project]) }
it { is_expected.to be_allowed(:read_terraform_state) }
it { is_expected.to be_allowed(:admin_terraform_state) }
end
end
end
...@@ -6,7 +6,8 @@ RSpec.describe 'query terraform states' do ...@@ -6,7 +6,8 @@ RSpec.describe 'query terraform states' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:terraform_state) { create(:terraform_state, :locked, project: project) } let_it_be(:terraform_state) { create(:terraform_state, :with_version, :locked, project: project) }
let_it_be(:latest_version) { terraform_state.latest_version }
let(:query) do let(:query) do
graphql_query_for(:project, { fullPath: project.full_path }, graphql_query_for(:project, { fullPath: project.full_path },
...@@ -20,6 +21,16 @@ RSpec.describe 'query terraform states' do ...@@ -20,6 +21,16 @@ RSpec.describe 'query terraform states' do
createdAt createdAt
updatedAt updatedAt
latestVersion {
id
createdAt
updatedAt
createdByUser {
id
}
}
lockedByUser { lockedByUser {
id id
} }
...@@ -37,13 +48,19 @@ RSpec.describe 'query terraform states' do ...@@ -37,13 +48,19 @@ RSpec.describe 'query terraform states' do
it 'returns terraform state data', :aggregate_failures do it 'returns terraform state data', :aggregate_failures do
state = data.dig('nodes', 0) state = data.dig('nodes', 0)
version = state['latestVersion']
expect(state['id']).to eq(terraform_state.to_global_id.to_s) expect(state['id']).to eq(terraform_state.to_global_id.to_s)
expect(state['name']).to eq(terraform_state.name) expect(state['name']).to eq(terraform_state.name)
expect(state['lockedAt']).to eq(terraform_state.locked_at.strftime('%Y-%m-%dT%H:%M:%SZ')) expect(state['lockedAt']).to eq(terraform_state.locked_at.iso8601)
expect(state['createdAt']).to eq(terraform_state.created_at.strftime('%Y-%m-%dT%H:%M:%SZ')) expect(state['createdAt']).to eq(terraform_state.created_at.iso8601)
expect(state['updatedAt']).to eq(terraform_state.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')) expect(state['updatedAt']).to eq(terraform_state.updated_at.iso8601)
expect(state.dig('lockedByUser', 'id')).to eq(terraform_state.locked_by_user.to_global_id.to_s) expect(state.dig('lockedByUser', 'id')).to eq(terraform_state.locked_by_user.to_global_id.to_s)
expect(version['id']).to eq(latest_version.to_global_id.to_s)
expect(version['createdAt']).to eq(terraform_state.created_at.iso8601)
expect(version['updatedAt']).to eq(terraform_state.updated_at.iso8601)
expect(version.dig('createdByUser', 'id')).to eq(latest_version.created_by_user.to_global_id.to_s)
end end
it 'returns count of terraform states' do it 'returns count of terraform states' do
......
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