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
This commit is contained in:
parent
265b491354
commit
6cf55e5129
|
@ -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"
|
||||
|
|
2
qa/qa.rb
2
qa/qa.rb
|
@ -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
|
||||
|
||||
repository.use_default_credentials
|
||||
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
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# 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
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# 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
|
|
@ -0,0 +1,31 @@
|
|||
# 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
|
|
@ -0,0 +1,40 @@
|
|||
# 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
|
Loading…
Reference in New Issue