diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 9584f2fcd8f4cee29758f64c433f3dd4e294582e..c721e9e3a77b4e3809aa8c9a6b66c5c5cbd5ca83 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -58,6 +58,9 @@ "GoogleCloudArtifactRegistryArtifact": [ "GoogleCloudArtifactRegistryDockerImage" ], + "GoogleCloudArtifactRegistryArtifactDetails": [ + "GoogleCloudArtifactRegistryDockerImageDetails" + ], "GoogleCloudLoggingConfigurationInterface": [ "GoogleCloudLoggingConfigurationType", "InstanceGoogleCloudLoggingConfigurationType" diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 07f8aad981e958e59628e20f597109d45426e62a..3586badc9d092e711a3cdcd1c18e3b8bd894b703 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -386,6 +386,26 @@ Whether Gitpod is enabled in application settings. Returns [`Boolean`](#boolean). +### `Query.googleCloudArtifactRegistryRepositoryArtifact` + +Details about an artifact in the Google Cloud Artifact Registry. + +DETAILS: +**Introduced** in GitLab 16.10. +**Status**: Experiment. + +Returns [`GoogleCloudArtifactRegistryArtifactDetails`](#googlecloudartifactregistryartifactdetails). + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="querygooglecloudartifactregistryrepositoryartifactgooglecloudprojectid"></a>`googleCloudProjectId` | [`String!`](#string) | ID of the Google Cloud project. | +| <a id="querygooglecloudartifactregistryrepositoryartifactimage"></a>`image` | [`String!`](#string) | Name of the image in the Google Cloud Artifact Registry. | +| <a id="querygooglecloudartifactregistryrepositoryartifactlocation"></a>`location` | [`String!`](#string) | Location of the Artifact Registry repository. | +| <a id="querygooglecloudartifactregistryrepositoryartifactprojectpath"></a>`projectPath` | [`ID!`](#id) | Full project path. | +| <a id="querygooglecloudartifactregistryrepositoryartifactrepository"></a>`repository` | [`String!`](#string) | Repository on the Google Cloud Artifact Registry. | + ### `Query.group` Find a group. @@ -19932,20 +19952,35 @@ Represents a docker artifact of Google Cloud Artifact Registry. | Name | Type | Description | | ---- | ---- | ----------- | -| <a id="googlecloudartifactregistrydockerimageartifactregistryimageurl"></a>`artifactRegistryImageUrl` | [`String!`](#string) | Google Cloud URL to access the image. | -| <a id="googlecloudartifactregistrydockerimagebuildtime"></a>`buildTime` | [`Time`](#time) | Time when the image was built. | | <a id="googlecloudartifactregistrydockerimagedigest"></a>`digest` | [`String!`](#string) | Image's digest. | | <a id="googlecloudartifactregistrydockerimageimage"></a>`image` | [`String!`](#string) | Image's name. | -| <a id="googlecloudartifactregistrydockerimageimagesizebytes"></a>`imageSizeBytes` | [`String`](#string) | Calculated size of the image. | -| <a id="googlecloudartifactregistrydockerimagelocation"></a>`location` | [`String!`](#string) | Location of the Artifact Registry repository. | -| <a id="googlecloudartifactregistrydockerimagemediatype"></a>`mediaType` | [`String`](#string) | Media type of the image. | | <a id="googlecloudartifactregistrydockerimagename"></a>`name` | [`String!`](#string) | Unique image name. | -| <a id="googlecloudartifactregistrydockerimageprojectid"></a>`projectId` | [`String!`](#string) | ID of the Google Cloud project. | -| <a id="googlecloudartifactregistrydockerimagerepository"></a>`repository` | [`String!`](#string) | Repository on the Google Cloud Artifact Registry. | | <a id="googlecloudartifactregistrydockerimagetags"></a>`tags` | [`[String!]`](#string) | Tags attached to the image. | | <a id="googlecloudartifactregistrydockerimageupdatetime"></a>`updateTime` | [`Time`](#time) | Time when the image was last updated. | | <a id="googlecloudartifactregistrydockerimageuploadtime"></a>`uploadTime` | [`Time`](#time) | Time when the image was uploaded. | -| <a id="googlecloudartifactregistrydockerimageuri"></a>`uri` | [`String!`](#string) | Google Cloud URI to access the image. | + +### `GoogleCloudArtifactRegistryDockerImageDetails` + +Represents details about docker artifact of Google Cloud Artifact Registry. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="googlecloudartifactregistrydockerimagedetailsartifactregistryimageurl"></a>`artifactRegistryImageUrl` | [`String!`](#string) | Google Cloud URL to access the image. | +| <a id="googlecloudartifactregistrydockerimagedetailsbuildtime"></a>`buildTime` | [`Time`](#time) | Time when the image was built. | +| <a id="googlecloudartifactregistrydockerimagedetailsdigest"></a>`digest` | [`String!`](#string) | Image's digest. | +| <a id="googlecloudartifactregistrydockerimagedetailsimage"></a>`image` | [`String!`](#string) | Image's name. | +| <a id="googlecloudartifactregistrydockerimagedetailsimagesizebytes"></a>`imageSizeBytes` | [`String`](#string) | Calculated size of the image. | +| <a id="googlecloudartifactregistrydockerimagedetailslocation"></a>`location` | [`String!`](#string) | Location of the Artifact Registry repository. | +| <a id="googlecloudartifactregistrydockerimagedetailsmediatype"></a>`mediaType` | [`String`](#string) | Media type of the image. | +| <a id="googlecloudartifactregistrydockerimagedetailsname"></a>`name` | [`String!`](#string) | Unique image name. | +| <a id="googlecloudartifactregistrydockerimagedetailsprojectid"></a>`projectId` | [`String!`](#string) | ID of the Google Cloud project. | +| <a id="googlecloudartifactregistrydockerimagedetailsrepository"></a>`repository` | [`String!`](#string) | Repository on the Google Cloud Artifact Registry. | +| <a id="googlecloudartifactregistrydockerimagedetailstags"></a>`tags` | [`[String!]`](#string) | Tags attached to the image. | +| <a id="googlecloudartifactregistrydockerimagedetailsupdatetime"></a>`updateTime` | [`Time`](#time) | Time when the image was last updated. | +| <a id="googlecloudartifactregistrydockerimagedetailsuploadtime"></a>`uploadTime` | [`Time`](#time) | Time when the image was uploaded. | +| <a id="googlecloudartifactregistrydockerimagedetailsuri"></a>`uri` | [`String!`](#string) | Google Cloud URI to access the image. | ### `GoogleCloudArtifactRegistryRepository` @@ -34270,6 +34305,14 @@ One of: - [`GoogleCloudArtifactRegistryDockerImage`](#googlecloudartifactregistrydockerimage) +#### `GoogleCloudArtifactRegistryArtifactDetails` + +Details type of Google Cloud Artifact Registry artifacts. + +One of: + +- [`GoogleCloudArtifactRegistryDockerImageDetails`](#googlecloudartifactregistrydockerimagedetails) + #### `Issuable` Represents an issuable. diff --git a/ee/app/graphql/ee/types/query_type.rb b/ee/app/graphql/ee/types/query_type.rb index 9dff7f9b56e7fd1a2e45c9450778fba979dfaf8e..498bc078080776054ea58f6f837cc4c7069d44f3 100644 --- a/ee/app/graphql/ee/types/query_type.rb +++ b/ee/app/graphql/ee/types/query_type.rb @@ -157,6 +157,12 @@ module QueryType null: true, description: 'Member roles available for the instance.', resolver: ::Resolvers::MemberRoles::RolesResolver, alpha: { milestone: '16.7' } + field :google_cloud_artifact_registry_repository_artifact, + ::Types::GoogleCloud::ArtifactRegistry::ArtifactDetailsType, + null: true, + description: 'Details about an artifact in the Google Cloud Artifact Registry.', + resolver: ::Resolvers::GoogleCloud::ArtifactRegistry::ArtifactResolver, + alpha: { milestone: '16.10' } end def vulnerability(id:) diff --git a/ee/app/graphql/resolvers/google_cloud/artifact_registry/artifact_resolver.rb b/ee/app/graphql/resolvers/google_cloud/artifact_registry/artifact_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef58f6858826c5492d6af37a73e2cfd23e74b95c --- /dev/null +++ b/ee/app/graphql/resolvers/google_cloud/artifact_registry/artifact_resolver.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module Resolvers + module GoogleCloud + module ArtifactRegistry + class ArtifactResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type ::Types::GoogleCloud::ArtifactRegistry::ArtifactDetailsType, null: true + + authorize :read_container_image + + argument :google_cloud_project_id, + GraphQL::Types::String, + required: true, + description: 'ID of the Google Cloud project.' + + argument :location, + GraphQL::Types::String, + required: true, + description: 'Location of the Artifact Registry repository.' + + argument :repository, + GraphQL::Types::String, + required: true, + description: 'Repository on the Google Cloud Artifact Registry.' + + argument :image, + GraphQL::Types::String, + required: true, + description: "Name of the image in the Google Cloud Artifact Registry." + + argument :project_path, + GraphQL::Types::ID, + required: true, + description: 'Full project path.' + + def ready?(google_cloud_project_id:, location:, repository:, image:, project_path:) + project_integration = find_project_integration!(project_path) + + validate_on_integration( + project_integration, + field: :artifact_registry_project_id, + value: google_cloud_project_id, + argument: :googleCloudProjectId, + field_title: s_('GoogleCloudPlatformService|Google Cloud project ID') + ) + + validate_on_integration( + project_integration, + field: :artifact_registry_location, + value: location, + argument: :location, + field_title: s_('GoogleCloudPlatformService|Repository location') + ) + + validate_on_integration( + project_integration, + field: :artifact_registry_repository, + value: repository, + argument: :repository, + field_title: s_('GoogleCloudPlatformService|Repository name') + ) + + super + end + + def resolve(google_cloud_project_id:, location:, repository:, image:, project_path:) + name = "projects/#{google_cloud_project_id}/locations/#{location}/repositories/#{repository}/" \ + "dockerImages/#{image}" + + response = ::GoogleCloudPlatform::ArtifactRegistry::GetDockerImageService.new( + current_user: current_user, + project: find_project!(project_path), + params: { + name: name + } + ).execute + + raise_resource_not_available_error!(response.message) unless response.success? + + response.payload + end + + private + + def find_project!(project_path) + strong_memoize_with(:find_project, project_path) do + authorized_find!(project_path) + end + end + + def find_project_integration!(project_path) + project = find_project!(project_path) + project_integration = project.google_cloud_platform_artifact_registry_integration + + unless project_integration + message = + ::GoogleCloudPlatform::ArtifactRegistry::GetDockerImageService::ERROR_RESPONSES[:no_project_integration] + .message + + raise_resource_not_available_error!(message) + end + + project_integration + end + + override :find_object + def find_object(full_path) + Project.find_by_full_path(full_path) + end + + def validate_on_integration(project_integration, field:, value:, argument:, field_title:) + return if value == project_integration.public_send(field) # rubocop:disable GitlabSecurity/PublicSend -- The `field` argument is considered safe + + raise_argument_error!( + argument_error_message( + argument, + title: field_title, + integration_title: project_integration.title + ) + ) + end + + def argument_error_message(argument, title:, integration_title:) + "`#{argument}` doesn't match #{title} of #{integration_title} integration" + end + + def raise_argument_error!(message) + raise Gitlab::Graphql::Errors::ArgumentError, message + end + end + end + end +end diff --git a/ee/app/graphql/types/google_cloud/artifact_registry/artifact_details_type.rb b/ee/app/graphql/types/google_cloud/artifact_registry/artifact_details_type.rb new file mode 100644 index 0000000000000000000000000000000000000000..13d4f3b2cd46459aef57f646649924d1c8327d35 --- /dev/null +++ b/ee/app/graphql/types/google_cloud/artifact_registry/artifact_details_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module GoogleCloud + module ArtifactRegistry + class ArtifactDetailsType < BaseUnion + graphql_name 'GoogleCloudArtifactRegistryArtifactDetails' + description 'Details type of Google Cloud Artifact Registry artifacts' + + possible_types ::Types::GoogleCloud::ArtifactRegistry::DockerImageDetailsType + + def self.resolve_type(object, _context) + case object + when Google::Cloud::ArtifactRegistry::V1::DockerImage + ::Types::GoogleCloud::ArtifactRegistry::DockerImageDetailsType + else + raise ::Gitlab::Graphql::Errors::BaseError, + "Unsupported Google Cloud Artifact Registry type #{object.class.name}" + end + end + end + end + end +end diff --git a/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_details_type.rb b/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_details_type.rb new file mode 100644 index 0000000000000000000000000000000000000000..8fd83f0a89aeaf6826ddab35cd31cdb130cb64d4 --- /dev/null +++ b/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_details_type.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module Types + module GoogleCloud + module ArtifactRegistry + # rubocop:disable Graphql/AuthorizeTypes -- authorization happens in the service, called from the resolver + class DockerImageDetailsType < DockerImageType + graphql_name 'GoogleCloudArtifactRegistryDockerImageDetails' + description 'Represents details about docker artifact of Google Cloud Artifact Registry' + + field :uri, + GraphQL::Types::String, + null: false, + description: 'Google Cloud URI to access the image.' + + field :image_size_bytes, + GraphQL::Types::String, + description: 'Calculated size of the image.' + + field :build_time, + Types::TimeType, + description: 'Time when the image was built.' + + field :media_type, + GraphQL::Types::String, + description: 'Media type of the image.' + + field :project_id, + GraphQL::Types::String, + null: false, + description: 'ID of the Google Cloud project.' + + field :location, + GraphQL::Types::String, + null: false, + description: 'Location of the Artifact Registry repository.' + + field :repository, + GraphQL::Types::String, + null: false, + description: 'Repository on the Google Cloud Artifact Registry.' + + field :artifact_registry_image_url, + GraphQL::Types::String, + null: false, + description: 'Google Cloud URL to access the image.' + + def build_time + return unless artifact.build_time + + Time.at(artifact.build_time.seconds) + end + + def artifact_registry_image_url + "https://#{artifact.uri}" + end + + def project_id + image_name_data[:project_id] + end + + def location + image_name_data[:location] + end + + def repository + image_name_data[:repository] + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_type.rb b/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_type.rb index 2bbcbef7940fafd76bf185da0c76c05f775e0362..05125f9933afb3f35c291df78a3c57da63e47723 100644 --- a/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_type.rb +++ b/ee/app/graphql/types/google_cloud/artifact_registry/docker_image_type.rb @@ -29,50 +29,18 @@ class DockerImageType < BaseObject null: false, description: 'Unique image name.' - field :uri, - GraphQL::Types::String, - null: false, - description: 'Google Cloud URI to access the image.' - field :tags, [GraphQL::Types::String], description: 'Tags attached to the image.' - field :image_size_bytes, - GraphQL::Types::String, - description: 'Calculated size of the image.' - field :upload_time, Types::TimeType, description: 'Time when the image was uploaded.' - field :media_type, - GraphQL::Types::String, - description: 'Media type of the image.' - - field :build_time, - Types::TimeType, - description: 'Time when the image was built.' - field :update_time, Types::TimeType, description: 'Time when the image was last updated.' - field :project_id, - GraphQL::Types::String, - null: false, - description: 'ID of the Google Cloud project.' - - field :location, - GraphQL::Types::String, - null: false, - description: 'Location of the Artifact Registry repository.' - - field :repository, - GraphQL::Types::String, - null: false, - description: 'Repository on the Google Cloud Artifact Registry.' - field :image, GraphQL::Types::String, null: false, @@ -83,33 +51,18 @@ class DockerImageType < BaseObject null: false, description: "Image's digest." - field :artifact_registry_image_url, - GraphQL::Types::String, - null: false, - description: 'Google Cloud URL to access the image.' - def upload_time return unless artifact.upload_time Time.at(artifact.upload_time.seconds) end - def build_time - return unless artifact.build_time - - Time.at(artifact.build_time.seconds) - end - def update_time return unless artifact.update_time Time.at(artifact.update_time.seconds) end - def artifact_registry_image_url - "https://#{artifact.uri}" - end - def image image_name_data[:image] end @@ -118,18 +71,6 @@ def digest image_name_data[:digest] end - def project_id - image_name_data[:project_id] - end - - def location - image_name_data[:location] - end - - def repository - image_name_data[:repository] - end - private def image_name_data diff --git a/ee/spec/graphql/types/google_cloud/artifact_registry/artifact_details_type_spec.rb b/ee/spec/graphql/types/google_cloud/artifact_registry/artifact_details_type_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0b1d5fa4a40373b46c878faf27e1490fb6a4492b --- /dev/null +++ b/ee/spec/graphql/types/google_cloud/artifact_registry/artifact_details_type_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'google/cloud/artifact_registry/v1' + +RSpec.describe GitlabSchema.types['GoogleCloudArtifactRegistryArtifactDetails'], feature_category: :container_registry do + describe '.resolve_type' do + let(:object) { Google::Cloud::ArtifactRegistry::V1::DockerImage.new(name: 'alpine') } + + subject(:mapping) { described_class.resolve_type(object, {}) } + + it { is_expected.to eq(::Types::GoogleCloud::ArtifactRegistry::DockerImageDetailsType) } + + context 'with an unknown type' do + let(:object) { {} } + + it 'raises the error' do + expect do + mapping + end.to raise_error(Gitlab::Graphql::Errors::BaseError, 'Unsupported Google Cloud Artifact Registry type Hash') + end + end + end +end diff --git a/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_details_type_spec.rb b/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_details_type_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0d0043f1592a0adcc0d9fbe0e2b0a7cc06bbc639 --- /dev/null +++ b/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_details_type_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['GoogleCloudArtifactRegistryDockerImageDetails'], feature_category: :container_registry do + specify do + expect(described_class.description) + .to eq('Represents details about docker artifact of Google Cloud Artifact Registry') + end + + it 'includes all expected fields' do + expected_fields = %w[ + name uri tags image_size_bytes upload_time + media_type build_time update_time project_id + location repository image digest artifact_registry_image_url + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_type_spec.rb b/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_type_spec.rb index 29f6cc89aae244d5e2b55cbb179e4a997ff09577..6deb362fb6b0c5f96cda82a30931379a56a05965 100644 --- a/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_type_spec.rb +++ b/ee/spec/graphql/types/google_cloud/artifact_registry/docker_image_type_spec.rb @@ -8,11 +8,7 @@ end it 'includes all expected fields' do - expected_fields = %w[ - name uri tags image_size_bytes upload_time - media_type build_time update_time project_id - location repository image digest artifact_registry_image_url - ] + expected_fields = %w[name tags upload_time update_time image digest] expect(described_class).to include_graphql_fields(*expected_fields) end diff --git a/ee/spec/graphql/types/query_type_spec.rb b/ee/spec/graphql/types/query_type_spec.rb index 42e5c8a5e45b659d4b896a4607e7ce7456fdc48a..65dcbf9cec0a7aee7a48f8cb3df5db84877cb793 100644 --- a/ee/spec/graphql/types/query_type_spec.rb +++ b/ee/spec/graphql/types/query_type_spec.rb @@ -37,7 +37,8 @@ :audit_events_instance_amazon_s3_configurations, :member_role, :self_managed_add_on_eligible_users, - :member_roles + :member_roles, + :google_cloud_artifact_registry_repository_artifact ] all_expected_fields = expected_foss_fields + expected_ee_fields diff --git a/ee/spec/requests/api/graphql/google_cloud/artifact_registry/docker_image_spec.rb b/ee/spec/requests/api/graphql/google_cloud/artifact_registry/docker_image_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d6ea5c0a2a6618a164b1e82651f706897a599d7 --- /dev/null +++ b/ee/spec/requests/api/graphql/google_cloud/artifact_registry/docker_image_spec.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'google/cloud/artifact_registry/v1' + +RSpec.describe 'getting the google cloud docker image linked to a project', :freeze_time, feature_category: :container_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + + let_it_be_with_refind(:project_integration) do + create( + :google_cloud_platform_artifact_registry_integration, + project: project, + artifact_registry_repositories: 'demo' + ) + end + + let_it_be(:user) { project.first_owner } + + let(:location) { project_integration.artifact_registry_location } + let(:google_cloud_project_id) { project_integration.artifact_registry_project_id } + let(:repository) { project_integration.artifact_registry_repository } + let(:image) { 'ruby' } + let(:digest) { 'sha256:4ca5c21b' } + let(:client_double) { instance_double('::GoogleCloudPlatform::ArtifactRegistry::Client') } + + let(:uri) do + "#{location}-docker.pkg.dev/#{google_cloud_project_id}/" \ + "#{project_integration.artifact_registry_repository}/#{image}@#{digest}" + end + + let(:name) do + "projects/#{google_cloud_project_id}/" \ + "locations/#{location}/" \ + "repositories/#{repository}/" \ + "dockerImages/#{image}@#{digest}" + end + + let(:docker_image) do + Google::Cloud::ArtifactRegistry::V1::DockerImage.new( + name: name, + uri: uri, + tags: ['97c58898'], + image_size_bytes: 304_121_628, + media_type: 'application/vnd.docker.distribution.manifest.v2+json', + build_time: Time.now, + update_time: Time.now, + upload_time: Time.now + ) + end + + let(:fields) do + <<~QUERY + #{query_graphql_fragment('GoogleCloudArtifactRegistryDockerImageDetails')} + QUERY + end + + let(:params) do + { + google_cloud_project_id: google_cloud_project_id, + location: location, + repository: repository, + image: "#{image}@#{digest}", + projectPath: project.full_path + } + end + + let(:query) do + graphql_query_for( + 'googleCloudArtifactRegistryRepositoryArtifact', params, fields + ) + end + + let(:artifact_response) do + graphql_data_at(:google_cloud_artifact_registry_repository_artifact) + end + + subject(:request) { post_graphql(query, current_user: user) } + + before do + stub_saas_features(google_cloud_support: true) + + allow(::GoogleCloudPlatform::ArtifactRegistry::Client).to receive(:new) + .with( + project_integration: project_integration, + user: user, + artifact_registry_location: location, + artifact_registry_repository: repository + ).and_return(client_double) + + allow(client_double).to receive(:docker_image).with(name: name).and_return(docker_image) + end + + shared_examples 'returning the expected response' do + it 'returns the proper response' do + request + + expect(artifact_response).to eq({ + 'name' => docker_image.name, + 'uri' => docker_image.uri, + 'tags' => docker_image.tags, + 'imageSizeBytes' => docker_image.image_size_bytes.to_s, + 'mediaType' => docker_image.media_type, + 'buildTime' => Time.now.iso8601, + 'updateTime' => Time.now.iso8601, + 'uploadTime' => Time.now.iso8601, + 'projectId' => google_cloud_project_id, + 'location' => location, + 'repository' => repository, + 'image' => image, + 'digest' => digest, + 'artifactRegistryImageUrl' => "https://#{uri}" + }) + end + end + + shared_examples 'returning a blank response' do + it 'returns a blank response' do + subject + + expect(artifact_response).to be_blank + end + end + + it_behaves_like 'a working graphql query' do + before do + request + end + end + + it 'matches the JSON schema' do + request + + expect(artifact_response).to match_schema('graphql/google_cloud/artifact_registry/docker_image_details') + end + + it_behaves_like 'returning the expected response' + + context 'when an user does not have required permissions' do + let(:user) { create(:user).tap { |user| project.add_guest(user) } } + + it_behaves_like 'returning a blank response' + end + + context 'when google artifact registry feature is unavailable' do + before do + stub_saas_features(google_cloud_support: false) + end + + it_behaves_like 'returning a blank response' + end + + context 'when gcp_artifact_registry FF is disabled' do + before do + stub_feature_flags(gcp_artifact_registry: false) + end + + it_behaves_like 'returning a blank response' + end + + context 'when Google Cloud Artifact Registry integration is not present' do + before do + project_integration.destroy! + end + + it_behaves_like 'returning a blank response' + end + + context 'when Google Cloud Artifact Registry integration is inactive' do + before do + project_integration.update_column(:active, false) + end + + it_behaves_like 'returning a blank response' + end + + context 'with invalid arguments' do + using RSpec::Parameterized::TableSyntax + + # rubocop:disable Layout/LineLength -- The table rows are more readable without line breaks + where(:argument, :error_message) do + :location | "`location` doesn't match Repository location of Google Cloud Artifact Registry" + :repository | "`repository` doesn't match Repository name of Google Cloud Artifact Registry" + :google_cloud_project_id | "`googleCloudProjectId` doesn't match Google Cloud project ID of Google Cloud Artifact Registry" + end + # rubocop:enable Layout/LineLength + + with_them do + let(params[:argument]) { 'invalid' } + + before do + request + end + + it 'returns the error' do + expect_graphql_errors_to_include(error_message) + end + end + end +end diff --git a/ee/spec/requests/api/graphql/project/google_cloud/artifact_registry/docker_images_spec.rb b/ee/spec/requests/api/graphql/project/google_cloud/artifact_registry/docker_images_spec.rb index a820e3523a4cdcc53d5f221d6cafc7d0ff6e8b68..210795a0740621e6a151d4448f3e6004cf7473a1 100644 --- a/ee/spec/requests/api/graphql/project/google_cloud/artifact_registry/docker_images_spec.rb +++ b/ee/spec/requests/api/graphql/project/google_cloud/artifact_registry/docker_images_spec.rb @@ -110,19 +110,11 @@ 'artifacts' => { 'nodes' => [{ 'name' => docker_image.name, - 'uri' => docker_image.uri, 'tags' => docker_image.tags, - 'imageSizeBytes' => docker_image.image_size_bytes.to_s, - 'mediaType' => docker_image.media_type, - 'buildTime' => Time.now.iso8601, - 'updateTime' => Time.now.iso8601, 'uploadTime' => Time.now.iso8601, - 'projectId' => project_integration.artifact_registry_project_id, - 'location' => project_integration.artifact_registry_location, - 'repository' => project_integration.artifact_registry_repository, + 'updateTime' => Time.now.iso8601, 'image' => image, - 'digest' => digest, - 'artifactRegistryImageUrl' => "https://#{docker_image.uri}" + 'digest' => digest }], 'pageInfo' => { 'endCursor' => end_cursor, diff --git a/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image.json b/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image.json index 59656c2b83ac4ae3e29e3eda9958d4162c76d46a..83a7154d32930d5108443c14000ecdb0004cdfa4 100644 --- a/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image.json +++ b/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image.json @@ -2,80 +2,39 @@ "type": "object", "required": [ "name", - "uri", "tags", - "imageSizeBytes", "uploadTime", - "mediaType", - "buildTime", "updateTime", - "projectId", - "location", - "repository", "image", - "digest", - "artifactRegistryImageUrl" + "digest" ], "properties": { "name": { "type": "string" }, - "uri": { - "type": "string" - }, "tags": { "type": "array", "items": { "type": "string" } }, - "imageSizeBytes": { - "type": [ - "string", - "null" - ] - }, "uploadTime": { "type": [ "string", "null" ] }, - "mediaType": { - "type": [ - "string", - "null" - ] - }, - "buildTime": { - "type": [ - "string", - "null" - ] - }, "updateTime": { "type": [ "string", "null" ] }, - "projectId": { - "type": "string" - }, - "location": { - "type": "string" - }, - "repository": { - "type": "string" - }, "image": { "type": "string" }, "digest": { "type": "string" - }, - "artifactRegistryUrl": { - "type": "string" } } } diff --git a/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image_details.json b/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image_details.json new file mode 100644 index 0000000000000000000000000000000000000000..9339f48604e4ed39f07c865cbb1d11cfc57aa2fe --- /dev/null +++ b/spec/fixtures/api/schemas/graphql/google_cloud/artifact_registry/docker_image_details.json @@ -0,0 +1,53 @@ +{ + "type": "object", + "allOf": [ + { + "$ref": "./docker_image.json" + } + ], + "required": [ + "uri", + "imageSizeBytes", + "buildTime", + "mediaType", + "projectId", + "location", + "repository", + "artifactRegistryImageUrl" + ], + "properties": { + "uri": { + "type": "string" + }, + "imageSizeBytes": { + "type": [ + "string", + "null" + ] + }, + "buildTime": { + "type": [ + "string", + "null" + ] + }, + "mediaType": { + "type": [ + "string", + "null" + ] + }, + "projectId": { + "type": "string" + }, + "location": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "artifactRegistryUrl": { + "type": "string" + } + } +}