Commit 6cf55e51 authored by Mark Lapierre's avatar Mark Lapierre Committed by Mark Lapierre

Add an SSH key and use it to clone and push

Adds 2 end-to-end tests:

1. Add and remove an SSH key
2. Add an SSH key and use it to clone and push

Includes changes to factories to allow Git actions via SSH
parent 265b4913
......@@ -158,7 +158,7 @@
- if project_nav_tab? :pipelines
= 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
= sprite_icon('rocket')
%span.nav-item-name
......
......@@ -5,10 +5,10 @@
.form-group
= 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.")
= 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
= 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')
.js-add-ssh-key-validation-warning.hide
......@@ -19,4 +19,4 @@
%button.btn.btn-create.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
.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 @@
= @key.key
.col-md-12
.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
autoload :Wiki, 'qa/factory/resource/wiki'
autoload :File, 'qa/factory/resource/file'
autoload :Fork, 'qa/factory/resource/fork'
autoload :SSHKey, 'qa/factory/resource/ssh_key'
end
module Repository
......@@ -217,6 +218,7 @@ module QA
module Profile
autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens'
autoload :SSHKeys, 'qa/page/profile/ssh_keys'
end
module Issuable
......
......@@ -11,7 +11,9 @@ module QA
factory.output
end
product(:project) { |factory| factory.project }
product :project do |factory|
factory.project
end
def initialize
@file_name = 'file.txt'
......@@ -21,8 +23,8 @@ module QA
@new_branch = true
end
def repository_uri
@repository_uri ||= begin
def repository_http_uri
@repository_http_uri ||= begin
project.visit!
Page::Project::Show.act do
choose_repository_clone_http
......@@ -30,6 +32,16 @@ module QA
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
......
......@@ -5,8 +5,8 @@ module QA
module Repository
class Push < Factory::Base
attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_uri,
:user
:branch_name, :new_branch, :output, :repository_http_uri,
:repository_ssh_uri, :ssh_key, :user
attr_writer :remote_branch
......@@ -16,7 +16,8 @@ module QA
@commit_message = "This is a test commit"
@branch_name = 'master'
@new_branch = true
@repository_uri = ""
@repository_http_uri = ""
@ssh_key = nil
end
def remote_branch
......@@ -31,9 +32,14 @@ module QA
def fabricate!
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
end
username = 'GitLab QA'
email = 'root@gitlab.com'
......@@ -63,6 +69,8 @@ module QA
repository.commit(commit_message)
@output = repository.push_changes("#{branch_name}:#{remote_branch}")
repository.delete_ssh_key
end
end
end
......
......@@ -16,8 +16,8 @@ module QA
@new_branch = false
end
def repository_uri
@repository_uri ||= begin
def repository_http_uri
@repository_http_uri ||= begin
wiki.visit!
Page::Project::Wiki::Show.act do
go_to_clone_repository
......
......@@ -20,6 +20,13 @@ module QA
end
end
product :repository_http_location do
Page::Project::Show.act do
choose_repository_clone_http
repository_location
end
end
def initialize
@description = 'My awesome project'
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
class Repository
include Scenario::Actable
def initialize
@ssh_cmd = ""
end
def self.perform(*args)
Dir.mktmpdir do |dir|
Dir.chdir(dir) { super }
......@@ -33,7 +37,7 @@ module QA
end
def clone(opts = '')
run_and_redact_credentials("git clone #{opts} #{@uri} ./")
run_and_redact_credentials(build_git_command("git clone #{opts} #{@uri} ./"))
end
def checkout(branch_name)
......@@ -53,6 +57,10 @@ module QA
`git config user.email #{email}`
end
def configure_ssh_command(command)
@ssh_cmd = "GIT_SSH_COMMAND='#{command}'"
end
def commit_file(name, contents, message)
add_file(name, contents)
commit(message)
......@@ -69,7 +77,7 @@ module QA
end
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
end
......@@ -78,6 +86,31 @@ module QA
`git log --oneline`.split("\n")
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
# Since the remote URL contains the credentials, and git occasionally
......
......@@ -6,6 +6,7 @@ module QA
element :access_token_link, 'link_to profile_personal_access_tokens_path'
element :access_token_title, 'Access Tokens'
element :top_level_items, '.sidebar-top-level-items'
element :ssh_keys, 'SSH Keys'
end
def click_access_tokens
......@@ -14,6 +15,12 @@ module QA
end
end
def click_ssh_keys
within_sidebar do
click_link('SSH Keys')
end
end
private
def within_sidebar
......
......@@ -6,6 +6,7 @@ module QA
element :settings_item
element :settings_link, 'link_to edit_project_path'
element :repository_link, "title: _('Repository')"
element :link_pipelines
element :pipelines_settings_link, "title: _('CI / CD')"
element :operations_kubernetes_link, "title: _('Kubernetes')"
element :issues_link, /link_to.*shortcuts-issues/
......@@ -49,7 +50,7 @@ module QA
def click_ci_cd_pipelines
within_sidebar do
click_link('CI / CD')
click_element :link_pipelines
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