Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate Git location sources #72

Merged
merged 7 commits into from Jul 2, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions features/install.feature
Expand Up @@ -70,3 +70,15 @@ Feature: install cookbooks from a Berksfile
Shims written to:
"""
And the exit status should be 0

Scenario: installing a Berksfile that has a Git location source with an invalid Git URI
Given I write to "Berksfile" with:
"""
cookbook "nginx", git: "/something/on/disk"
"""
When I run the install command
Then the output should contain:
"""
'/something/on/disk' is not a valid Git URI.
"""
And the CLI should exit with the status code for error "InvalidGitURI"
2 changes: 2 additions & 0 deletions lib/berkshelf/cookbook_source/git_location.rb
Expand Up @@ -11,6 +11,8 @@ def initialize(name, options)
@name = name
@uri = options[:git]
@branch = options[:branch] || options[:ref] || options[:tag]

Git.validate_uri!(@uri)
end

def download(destination)
Expand Down
25 changes: 25 additions & 0 deletions lib/berkshelf/errors.rb
@@ -1,11 +1,14 @@
module Berkshelf
class BerkshelfError < StandardError
class << self
# @param [Integer] code
def status_code(code)
define_method(:status_code) { code }
define_singleton_method(:status_code) { code }
end
end

alias_method :message, :to_s
end

class BerksfileNotFound < BerkshelfError; status_code(100); end
Expand All @@ -18,4 +21,26 @@ class NoSolution < BerkshelfError; status_code(106); end
class CookbookSyntaxError < BerkshelfError; status_code(107); end
class UploadFailure < BerkshelfError; status_code(108); end
class KnifeConfigNotFound < BerkshelfError; status_code(109); end

class InvalidGitURI < BerkshelfError
status_code(110)
attr_reader :uri

# @param [String] uri
def initialize(uri)
@uri = uri
end

def to_s
"'#{uri}' is not a valid Git URI."
end
end

class GitNotFound < BerkshelfError
status_code(110)

def to_s
"Could not find a Git executable in your path. Please add it and try again."
end
end
end
78 changes: 75 additions & 3 deletions lib/berkshelf/git.rb
@@ -1,6 +1,19 @@
require 'uri'

module Berkshelf
# @author Jamie Winsor <jamie@vialstudios.com>
class Git
GIT_REGEXP = URI.regexp(%w{ https git })
SSH_REGEXP = /(.+)@(.+):(.+)\/(.+)\.git/

class << self
# @overload git(commands)
# Shellout to the Git executable on your system with the given commands.
#
# @param [Array<String>]
#
# @return [String]
# the output of the execution of the Git command
def git(*command)
out = quietly {
%x{ #{git_cmd} #{command.join(' ')} }
Expand All @@ -10,6 +23,15 @@ def git(*command)
out.chomp
end

# Clone a remote Git repository to disk
#
# @param [String] uri
# a Git URI to clone
# @param [#to_s] destination
# a local path on disk to clone to
#
# @return [String]
# the destination the URI was cloned to
def clone(uri, destination = Dir.mktmpdir)
git("clone", uri, destination.to_s)

Expand All @@ -18,22 +40,31 @@ def clone(uri, destination = Dir.mktmpdir)
destination
end

# Checkout the given reference in the given repository
#
# @param [String] repo_path
# path to a Git repo on disk
# @param [String] ref
# reference to checkout
def checkout(repo_path, ref)
Dir.chdir repo_path do
git("checkout", "-q", ref)
end
end

# @param [Strin] repo_path
def rev_parse(repo_path)
Dir.chdir repo_path do
git("rev-parse", "HEAD")
end
end

# Return an absolute path to the Git executable on your system
#
# This is to defeat aliases/shell functions called 'git' and a number of
# other problems.
# @return [String]
# absolute path to git executable
#
# @raise [GitNotFound] if executable is not found in system path
def find_git
git_path = nil
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
Expand All @@ -50,12 +81,53 @@ def find_git
end

unless git_path
raise "Could not find git. Please ensure it is in your path."
raise GitNotFound
end

return git_path
end

# Determines if the given URI is a valid Git URI. A valid Git URI is a string
# containing the location of a Git repository by either the Git protocol,
# SSH protocol, or HTTPS protocol.
#
# @example Valid Git protocol URI
# "git://github.com/reset/thor-foodcritic.git"
# @example Valid HTTPS URI
# "https://github.com/reset/solve.git"
# @example Valid SSH protocol URI
# "git@github.com:reset/solve.git"
#
# @param [String] uri
#
# @return [Boolean]
def validate_uri(uri)
unless uri.is_a?(String)
return false
end

unless uri.slice(SSH_REGEXP).nil?
return true
end

unless uri.slice(GIT_REGEXP).nil?
return true
end

false
end

# @raise [InvalidGitURI] if the given object is not a String containing a valid Git URI
#
# @see validate_uri
def validate_uri!(uri)
unless validate_uri(uri)
raise InvalidGitURI.new(uri)
end

true
end

private

def git_cmd
Expand Down
14 changes: 13 additions & 1 deletion spec/unit/berkshelf/cookbook_source/git_location_spec.rb
Expand Up @@ -2,7 +2,19 @@

module Berkshelf
describe CookbookSource::GitLocation do
subject { CookbookSource::GitLocation.new("nginx", :git => "git://github.com/opscode-cookbooks/nginx.git") }
describe "ClassMethods" do
subject { CookbookSource::GitLocation }

describe "::initialize" do
it "raises InvalidGitURI if given an invalid Git URI for options[:git]" do
lambda {
subject.new("nginx", git: "/something/on/disk")
}.should raise_error(InvalidGitURI)
end
end
end

subject { CookbookSource::GitLocation.new("nginx", git: "git://github.com/opscode-cookbooks/nginx.git") }

describe "#download" do
it "downloads the cookbook to the given destination" do
Expand Down
109 changes: 97 additions & 12 deletions spec/unit/berkshelf/git_spec.rb
Expand Up @@ -5,24 +5,21 @@ module Berkshelf
describe "ClassMethods" do
subject { Git }

describe "#find_git" do
describe "::find_git" do
it "should find git" do
subject.find_git.should_not be_nil
end

it "should raise if it can't find git" do
begin
path = ENV["PATH"]
ENV["PATH"] = ""

lambda { subject.find_git }.should raise_error
ensure
ENV["PATH"] = path
end
ENV.should_receive(:[]).with("PATH").and_return(String.new)

lambda {
subject.find_git
}.should raise_error(GitNotFound)
end
end

describe "#clone" do
describe "::clone" do
let(:target) { tmp_path.join("nginx") }

it "clones the repository to the target path" do
Expand All @@ -33,7 +30,7 @@ module Berkshelf
end
end

describe "#checkout" do
describe "::checkout" do
let(:repo_path) { tmp_path.join("nginx") }
let(:repo) { subject.clone("git://github.com/opscode-cookbooks/nginx.git", repo_path) }
let(:tag) { "0.101.2" }
Expand All @@ -47,7 +44,7 @@ module Berkshelf
end
end

describe "#rev_parse" do
describe "::rev_parse" do
let(:repo_path) { tmp_path.join("nginx") }
before(:each) do
subject.clone("git://github.com/opscode-cookbooks/nginx.git", repo_path)
Expand All @@ -58,6 +55,94 @@ module Berkshelf
subject.rev_parse(repo_path).should eql("0e4887d9eef8cb83972f974a85890983c8204c3b")
end
end

let(:readonly_uri) { "git://github.com/reset/thor-foodcritic.git" }
let(:https_uri) { "https://github.com/reset/solve.git" }
let(:ssh_uri) { "git@github.com:reset/solve.git" }
let(:http_uri) { "http://github.com/reset/solve.git" }
let(:invalid_uri) { "/something/on/disk" }

describe "::validate_uri" do
context "given a valid Git read-only URI" do
it "returns true" do
subject.validate_uri(readonly_uri)
end
end

context "given a valid Git HTTPS URI" do
it "returns true" do
subject.validate_uri(https_uri)
end
end

context "given a valid Git SSH URI" do
it "returns true" do
subject.validate_uri(ssh_uri)
end
end

context "given an invalid URI" do
it "returns false" do
subject.validate_uri(invalid_uri)
end
end

context "given a HTTP URI" do
it "returns false" do
subject.validate_uri(http_uri)
end
end

context "given an integer" do
it "returns false" do
subject.validate_uri(123)
end
end
end

describe "::validate_uri!" do
context "given a valid Git read-only URI" do
it "returns true" do
subject.validate_uri!(readonly_uri)
end
end

context "given a valid Git HTTPS URI" do
it "returns true" do
subject.validate_uri!(https_uri)
end
end

context "given a valid Git SSH URI" do
it "returns true" do
subject.validate_uri!(ssh_uri)
end
end

context "given an invalid URI" do
it "raises InvalidGitURI" do
lambda {
subject.validate_uri!(invalid_uri)
}.should raise_error(InvalidGitURI)
end
end

context "given a HTTP URI" do
it "raises InvalidGitURI" do
lambda {
subject.validate_uri!(http_uri)
}.should raise_error(InvalidGitURI)
end
end

context "given an integer" do
it "raises InvalidGitURI" do
lambda {
subject.validate_uri!(123)
}.should raise_error(InvalidGitURI)
end
end
end
end
end
end