Commit c44d8bab authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ml-qa-spec-use-ssh-key' into 'master'

Add a new scenario to add an SSH key, perform Git actions with it, and then remove the key

See merge request gitlab-org/gitlab-ce!19754
parents cc2dab3b 6cf55e51
...@@ -158,7 +158,7 @@ ...@@ -158,7 +158,7 @@
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines' do
.nav-icon-container .nav-icon-container
= sprite_icon('rocket') = sprite_icon('rocket')
%span.nav-item-name %span.nav-item-name
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
.form-group .form-group
= f.label :key, class: 'label-bold' = f.label :key, class: 'label-bold'
%p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.") %p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.")
= f.text_area :key, class: "form-control js-add-ssh-key-validation-input", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"') = f.text_area :key, class: "form-control js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"')
.form-group .form-group
= f.label :title, class: 'label-bold' = f.label :title, class: 'label-bold'
= f.text_field :title, class: "form-control input-lg", required: true, placeholder: s_('Profiles|e.g. My MacBook key') = f.text_field :title, class: "form-control input-lg qa-key-title-field", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
%p.form-text.text-muted= _('Name your individual key via a title') %p.form-text.text-muted= _('Name your individual key via a title')
.js-add-ssh-key-validation-warning.hide .js-add-ssh-key-validation-warning.hide
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
%button.btn.btn-create.js-add-ssh-key-validation-confirm-submit= _("Yes, add it") %button.btn.btn-create.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
.prepend-top-default .prepend-top-default
= f.submit s_('Profiles|Add key'), class: "btn btn-create js-add-ssh-key-validation-original-submit" = f.submit s_('Profiles|Add key'), class: "btn btn-create js-add-ssh-key-validation-original-submit qa-add-key-button"
...@@ -24,4 +24,4 @@ ...@@ -24,4 +24,4 @@
= @key.key = @key.key
.col-md-12 .col-md-12
.float-right .float-right
= link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
...@@ -57,6 +57,7 @@ module QA ...@@ -57,6 +57,7 @@ module QA
autoload :Wiki, 'qa/factory/resource/wiki' autoload :Wiki, 'qa/factory/resource/wiki'
autoload :File, 'qa/factory/resource/file' autoload :File, 'qa/factory/resource/file'
autoload :Fork, 'qa/factory/resource/fork' autoload :Fork, 'qa/factory/resource/fork'
autoload :SSHKey, 'qa/factory/resource/ssh_key'
end end
module Repository module Repository
...@@ -217,6 +218,7 @@ module QA ...@@ -217,6 +218,7 @@ module QA
module Profile module Profile
autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens' autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens'
autoload :SSHKeys, 'qa/page/profile/ssh_keys'
end end
module Issuable module Issuable
......
...@@ -11,7 +11,9 @@ module QA ...@@ -11,7 +11,9 @@ module QA
factory.output factory.output
end end
product(:project) { |factory| factory.project } product :project do |factory|
factory.project
end
def initialize def initialize
@file_name = 'file.txt' @file_name = 'file.txt'
...@@ -21,8 +23,8 @@ module QA ...@@ -21,8 +23,8 @@ module QA
@new_branch = true @new_branch = true
end end
def repository_uri def repository_http_uri
@repository_uri ||= begin @repository_http_uri ||= begin
project.visit! project.visit!
Page::Project::Show.act do Page::Project::Show.act do
choose_repository_clone_http choose_repository_clone_http
...@@ -30,6 +32,16 @@ module QA ...@@ -30,6 +32,16 @@ module QA
end end
end end
end end
def repository_ssh_uri
@repository_ssh_uri ||= begin
project.visit!
Page::Project::Show.act do
choose_repository_clone_ssh
repository_location.uri
end
end
end
end end
end end
end end
......
...@@ -5,8 +5,8 @@ module QA ...@@ -5,8 +5,8 @@ module QA
module Repository module Repository
class Push < Factory::Base class Push < Factory::Base
attr_accessor :file_name, :file_content, :commit_message, attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_uri, :branch_name, :new_branch, :output, :repository_http_uri,
:user :repository_ssh_uri, :ssh_key, :user
attr_writer :remote_branch attr_writer :remote_branch
...@@ -16,7 +16,8 @@ module QA ...@@ -16,7 +16,8 @@ module QA
@commit_message = "This is a test commit" @commit_message = "This is a test commit"
@branch_name = 'master' @branch_name = 'master'
@new_branch = true @new_branch = true
@repository_uri = "" @repository_http_uri = ""
@ssh_key = nil
end end
def remote_branch def remote_branch
...@@ -31,9 +32,14 @@ module QA ...@@ -31,9 +32,14 @@ module QA
def fabricate! def fabricate!
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.uri = repository_uri if ssh_key
repository.uri = repository_ssh_uri
repository.use_ssh_key(ssh_key)
else
repository.uri = repository_http_uri
repository.use_default_credentials repository.use_default_credentials
end
username = 'GitLab QA' username = 'GitLab QA'
email = 'root@gitlab.com' email = 'root@gitlab.com'
...@@ -63,6 +69,8 @@ module QA ...@@ -63,6 +69,8 @@ module QA
repository.commit(commit_message) repository.commit(commit_message)
@output = repository.push_changes("#{branch_name}:#{remote_branch}") @output = repository.push_changes("#{branch_name}:#{remote_branch}")
repository.delete_ssh_key
end end
end end
end end
......
...@@ -16,8 +16,8 @@ module QA ...@@ -16,8 +16,8 @@ module QA
@new_branch = false @new_branch = false
end end
def repository_uri def repository_http_uri
@repository_uri ||= begin @repository_http_uri ||= begin
wiki.visit! wiki.visit!
Page::Project::Wiki::Show.act do Page::Project::Wiki::Show.act do
go_to_clone_repository go_to_clone_repository
......
...@@ -20,6 +20,13 @@ module QA ...@@ -20,6 +20,13 @@ module QA
end end
end end
product :repository_http_location do
Page::Project::Show.act do
choose_repository_clone_http
repository_location
end
end
def initialize def initialize
@description = 'My awesome project' @description = 'My awesome project'
end end
......
# frozen_string_literal: true
module QA
module Factory
module Resource
class SSHKey < Factory::Base
extend Forwardable
attr_accessor :title
attr_reader :private_key, :public_key, :fingerprint
def_delegators :key, :private_key, :public_key, :fingerprint
product :private_key do |factory|
factory.private_key
end
product :title do |factory|
factory.title
end
product :fingerprint do |factory|
factory.fingerprint
end
def key
@key ||= Runtime::Key::RSA.new
end
def fabricate!
Page::Menu::Main.act { go_to_profile_settings }
Page::Menu::Profile.act { click_ssh_keys }
Page::Profile::SSHKeys.perform do |page|
page.add_key(public_key, title)
end
end
end
end
end
end
...@@ -7,6 +7,10 @@ module QA ...@@ -7,6 +7,10 @@ module QA
class Repository class Repository
include Scenario::Actable include Scenario::Actable
def initialize
@ssh_cmd = ""
end
def self.perform(*args) def self.perform(*args)
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
Dir.chdir(dir) { super } Dir.chdir(dir) { super }
...@@ -38,7 +42,7 @@ module QA ...@@ -38,7 +42,7 @@ module QA
end end
def clone(opts = '') def clone(opts = '')
run_and_redact_credentials("git clone #{opts} #{@uri} ./") run_and_redact_credentials(build_git_command("git clone #{opts} #{@uri} ./"))
end end
def checkout(branch_name) def checkout(branch_name)
...@@ -58,6 +62,10 @@ module QA ...@@ -58,6 +62,10 @@ module QA
`git config user.email #{email}` `git config user.email #{email}`
end end
def configure_ssh_command(command)
@ssh_cmd = "GIT_SSH_COMMAND='#{command}'"
end
def commit_file(name, contents, message) def commit_file(name, contents, message)
add_file(name, contents) add_file(name, contents)
commit(message) commit(message)
...@@ -74,7 +82,7 @@ module QA ...@@ -74,7 +82,7 @@ module QA
end end
def push_changes(branch = 'master') def push_changes(branch = 'master')
output, _ = run_and_redact_credentials("git push #{@uri} #{branch}") output, _ = run_and_redact_credentials(build_git_command("git push #{@uri} #{branch}"))
output output
end end
...@@ -83,6 +91,31 @@ module QA ...@@ -83,6 +91,31 @@ module QA
`git log --oneline`.split("\n") `git log --oneline`.split("\n")
end end
def use_ssh_key(key)
@private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
File.binwrite(@private_key_file, key.private_key)
File.chmod(0700, @private_key_file)
@known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
keyscan_params = ['-H']
keyscan_params << "-p #{@uri.port}" if @uri.port
keyscan_params << @uri.host
run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{@known_hosts_file.path}")
configure_ssh_command("ssh -i #{@private_key_file.path} -o UserKnownHostsFile=#{@known_hosts_file.path}")
end
def delete_ssh_key
return unless @private_key_file
@private_key_file.close(true)
@known_hosts_file.close(true)
end
def build_git_command(command_str)
[@ssh_cmd, command_str].compact.join(' ')
end
private private
# Since the remote URL contains the credentials, and git occasionally # Since the remote URL contains the credentials, and git occasionally
......
...@@ -6,6 +6,7 @@ module QA ...@@ -6,6 +6,7 @@ module QA
element :access_token_link, 'link_to profile_personal_access_tokens_path' element :access_token_link, 'link_to profile_personal_access_tokens_path'
element :access_token_title, 'Access Tokens' element :access_token_title, 'Access Tokens'
element :top_level_items, '.sidebar-top-level-items' element :top_level_items, '.sidebar-top-level-items'
element :ssh_keys, 'SSH Keys'
end end
def click_access_tokens def click_access_tokens
...@@ -14,6 +15,12 @@ module QA ...@@ -14,6 +15,12 @@ module QA
end end
end end
def click_ssh_keys
within_sidebar do
click_link('SSH Keys')
end
end
private private
def within_sidebar def within_sidebar
......
...@@ -6,6 +6,7 @@ module QA ...@@ -6,6 +6,7 @@ module QA
element :settings_item element :settings_item
element :settings_link, 'link_to edit_project_path' element :settings_link, 'link_to edit_project_path'
element :repository_link, "title: _('Repository')" element :repository_link, "title: _('Repository')"
element :link_pipelines
element :pipelines_settings_link, "title: _('CI / CD')" element :pipelines_settings_link, "title: _('CI / CD')"
element :operations_kubernetes_link, "title: _('Kubernetes')" element :operations_kubernetes_link, "title: _('Kubernetes')"
element :issues_link, /link_to.*shortcuts-issues/ element :issues_link, /link_to.*shortcuts-issues/
...@@ -49,7 +50,7 @@ module QA ...@@ -49,7 +50,7 @@ module QA
def click_ci_cd_pipelines def click_ci_cd_pipelines
within_sidebar do within_sidebar do
click_link('CI / CD') click_element :link_pipelines
end end
end end
......
# frozen_string_literal: true
module QA
module Page
module Profile
class SSHKeys < Page::Base
view 'app/views/profiles/keys/_form.html.haml' do
element :key_title_field
element :key_public_key_field
element :add_key_button
end
view 'app/views/profiles/keys/_key_details.html.haml' do
element :delete_key_button
end
def add_key(public_key, title)
fill_element :key_public_key_field, public_key
fill_element :key_title_field, title
click_element :add_key_button
end
def remove_key(title)
click_link(title)
accept_alert do
click_element :delete_key_button
end
end
end
end
end
end
# frozen_string_literal: true
module QA
context :create do
describe 'SSH keys support' do
let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
it 'user adds and then removes an SSH key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
key = Factory::Resource::SSHKey.fabricate! do |resource|
resource.title = key_title
end
expect(page).to have_content("Title: #{key_title}")
expect(page).to have_content(key.fingerprint)
Page::Menu::Main.act { go_to_profile_settings }
Page::Menu::Profile.act { click_ssh_keys }
Page::Profile::SSHKeys.perform do |ssh_keys|
ssh_keys.remove_key(key_title)
end
expect(page).not_to have_content("Title: #{key_title}")
expect(page).not_to have_content(key.fingerprint)
end
end
end
end
# frozen_string_literal: true
module QA
context :create do
describe 'SSH key support' do
# Note: If you run this test against GDK make sure you've enabled sshd
# See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md
let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
it 'user adds an ssh key and pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
key = Factory::Resource::SSHKey.fabricate! do |resource|
resource.title = key_title
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.ssh_key = key
push.file_name = 'README.md'
push.file_content = '# Test Use SSH Key'
push.commit_message = 'Add README.md'
end
Page::Project::Show.act { wait_for_push }
expect(page).to have_content('README.md')
expect(page).to have_content('Test Use SSH Key')
Page::Menu::Main.act { go_to_profile_settings }
Page::Menu::Profile.act { click_ssh_keys }
Page::Profile::SSHKeys.perform do |ssh_keys|
ssh_keys.remove_key(key_title)
end
end
end
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