diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/git/CloneGitRepo.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/git/CloneGitRepo.java new file mode 100644 index 0000000000..2d555ab4dc --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/git/CloneGitRepo.java @@ -0,0 +1,215 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.scriptbuilder.statements.git; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.URI; + +import org.jclouds.scriptbuilder.domain.OsFamily; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.Statements; + +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Clones a repository into a newly created directory, creates remote-tracking branches for each + * branch in the cloned repository (visible using git branch -r), and creates and checks out an + * initial branch that is forked from the cloned repository's currently active branch. PWD is set to + * the directory being checked out. + * + * @author Adrian Cole + */ +public class CloneGitRepo implements Statement { + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder().fromCloneGitRepo(this); + } + + public static class Builder { + + protected URI repository; + protected Optional branch = Optional.absent(); + protected Optional tag = Optional.absent(); + protected Optional directory = Optional.absent(); + + /** + * @see CloneGitRepo#getRepository() + */ + public Builder repository(URI repository) { + this.repository = repository; + return this; + } + + /** + * @see CloneGitRepo#getRepository() + */ + public Builder repository(String repository) { + return repository(URI.create(repository)); + } + + /** + * @see CloneGitRepo#getBranch() + */ + public Builder branch(String branch) { + this.branch = Optional.fromNullable(branch); + return this; + } + + /** + * @see CloneGitRepo#getTag() + */ + public Builder tag(String tag) { + this.tag = Optional.fromNullable(tag); + return this; + } + + /** + * @see CloneGitRepo#getDirectory() + */ + public Builder directory(String directory) { + this.directory = Optional.fromNullable(directory); + return this; + } + + public CloneGitRepo build() { + return new CloneGitRepo(repository, branch, tag, directory); + } + + public Builder fromCloneGitRepo(CloneGitRepo in) { + return this.repository(in.getRepository()).branch(in.getBranch().orNull()).tag(in.getTag().orNull()) + .directory(in.getDirectory().orNull()); + } + } + + protected final URI repository; + protected final Optional branch; + protected final Optional tag; + protected final Optional directory; + + protected CloneGitRepo(URI repository, Optional branch, Optional tag, Optional directory) { + this.repository = checkNotNull(repository, "repository"); + this.branch = checkNotNull(branch, "branch"); + this.tag = checkNotNull(tag, "tag"); + this.directory = checkNotNull(directory, "directory"); + } + + /** + * The (possibly remote) repository to clone from. + */ + public URI getRepository() { + return repository; + } + + /** + * Instead of pointing the newly created HEAD to the branch pointed to by the cloned repository's + * HEAD, point to this branch instead. In a non-bare repository, this is the branch that will be + * checked out. + */ + public Optional getBranch() { + return branch; + } + + /** + * checkout the following tag on the branch + */ + public Optional getTag() { + return tag; + } + + /** + * The name of a new directory to clone into. The "humanish" part of the source repository is + * used if no directory is explicitly given (repo for /path/to/repo.git and foo for + * host.xz:foo/.git). + */ + public Optional getDirectory() { + return directory; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(repository, branch, tag, directory); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CloneGitRepo other = CloneGitRepo.class.cast(obj); + return Objects.equal(this.repository, other.repository) && Objects.equal(this.branch, other.branch) + && Objects.equal(this.tag, other.tag) && Objects.equal(this.directory, other.directory); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable functionDependencies(OsFamily arg0) { + return ImmutableSet. of(); + } + + /** + * {@inheritDoc} + */ + @Override + public String render(OsFamily arg0) { + StringBuilder command = new StringBuilder(); + command.append("git clone"); + if (branch.isPresent()) + command.append(" -b ").append(branch.get()); + command.append(' ').append(repository.toASCIIString()); + if (directory.isPresent()) + command.append(' ').append(directory.get()); + command.append("{lf}"); + command.append("{cd} ").append( + directory.or(Iterables.getLast(Splitter.on('/').split(repository.getPath())).replace(".git", ""))); + if (tag.isPresent()) { + command.append("{lf}").append("git checkout ").append(tag.get()); + } + return Statements.exec(command.toString()).render(arg0); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues().add("repository", repository).add("branch", branch.orNull()) + .add("tag", tag.orNull()).add("directory", directory.orNull()).toString(); + } + +} diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/git/InstallGit.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/git/InstallGit.java new file mode 100644 index 0000000000..8de5690752 --- /dev/null +++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/git/InstallGit.java @@ -0,0 +1,36 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.scriptbuilder.statements.git; + +import static org.jclouds.scriptbuilder.domain.Statements.call; + +import org.jclouds.scriptbuilder.domain.StatementList; + +/** + * Installs git onto a host + * + * @author Adrian Cole + */ +public class InstallGit extends StatementList { + + public InstallGit() { + super(call("setupPublicCurl"), call("installGit")); + } + +} \ No newline at end of file diff --git a/scriptbuilder/src/main/resources/functions/installGit.sh b/scriptbuilder/src/main/resources/functions/installGit.sh new file mode 100644 index 0000000000..1b1e50e1de --- /dev/null +++ b/scriptbuilder/src/main/resources/functions/installGit.sh @@ -0,0 +1,11 @@ +function installGit() { + if hash apt-get 2>/dev/null; then + ensure_cmd_or_install_package_apt git git-core + elif hash yum 2>/dev/null; then + ensure_cmd_or_install_package_yum git git-core + else + abort "we only support apt-get and yum right now... please contribute!" + return 1 + fi + return 0 +} diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/git/CloneGitRepoTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/git/CloneGitRepoTest.java new file mode 100644 index 0000000000..0e099280c3 --- /dev/null +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/git/CloneGitRepoTest.java @@ -0,0 +1,54 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.scriptbuilder.statements.git; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.scriptbuilder.domain.OsFamily; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "CloneGitRepoTest") +public class CloneGitRepoTest { + + public void testUNIX() { + assertEquals(CloneGitRepo.builder().repository("https://github.com/joyent/node.git").build() + .render(OsFamily.UNIX), "git clone https://github.com/joyent/node.git\ncd node\n"); + } + + public void testWithBranchUNIX() { + assertEquals(CloneGitRepo.builder().repository("https://github.com/joyent/node.git").branch("v0.6").build() + .render(OsFamily.UNIX), "git clone -b v0.6 https://github.com/joyent/node.git\ncd node\n"); + + } + + public void testWithBranchAndTagUNIX() { + assertEquals(CloneGitRepo.builder().repository("https://github.com/joyent/node.git").branch("v0.6") + .tag("v0.6.10").build().render(OsFamily.UNIX), + "git clone -b v0.6 https://github.com/joyent/node.git\ncd node\ngit checkout v0.6.10\n"); + } + + public void testWithDirectoryUNIX() { + assertEquals(CloneGitRepo.builder().repository("https://github.com/joyent/node.git").directory("/tmp/node-local") + .build().render(OsFamily.UNIX), + "git clone https://github.com/joyent/node.git /tmp/node-local\ncd /tmp/node-local\n"); + } +} diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/git/InstallGitTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/git/InstallGitTest.java new file mode 100644 index 0000000000..12ee9e203b --- /dev/null +++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/git/InstallGitTest.java @@ -0,0 +1,52 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.scriptbuilder.statements.git; + +import static org.testng.Assert.assertEquals; + +import java.io.IOException; + +import org.jclouds.scriptbuilder.InitScript; +import org.jclouds.scriptbuilder.domain.OsFamily; +import org.jclouds.scriptbuilder.domain.ShellToken; +import org.jclouds.scriptbuilder.statements.git.InstallGit; +import org.testng.annotations.Test; + +import com.google.common.base.Charsets; +import com.google.common.io.CharStreams; +import com.google.common.io.Resources; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "InstallGitTest") +public class InstallGitTest { + + public void testInstallGitUNIX() throws IOException { + assertEquals(new InstallGit().render(OsFamily.UNIX), "setupPublicCurl || return 1\ninstallGit || return 1\n"); + } + + public void testInstallGitUNIXInScriptBuilderSourcesSetupPublicCurl() throws IOException { + assertEquals(InitScript.builder().name("install_git").run(new InstallGit()).build().render(OsFamily.UNIX), + CharStreams.toString(Resources.newReaderSupplier( + Resources.getResource("test_install_git_scriptbuilder." + ShellToken.SH.to(OsFamily.UNIX)), + Charsets.UTF_8))); + } + +} diff --git a/scriptbuilder/src/test/resources/test_install_git_scriptbuilder.sh b/scriptbuilder/src/test/resources/test_install_git_scriptbuilder.sh new file mode 100644 index 0000000000..43f8a1b026 --- /dev/null +++ b/scriptbuilder/src/test/resources/test_install_git_scriptbuilder.sh @@ -0,0 +1,211 @@ +#!/bin/bash +set +u +shopt -s xpg_echo +shopt -s expand_aliases +unset PATH JAVA_HOME LD_LIBRARY_PATH +function abort { + echo "aborting: $@" 1>&2 + exit 1 +} +function default { + export INSTANCE_NAME="install_git" +export INSTANCE_HOME="/tmp/$INSTANCE_NAME" +export LOG_DIR="$INSTANCE_HOME" + return $? +} +function install_git { + return $? +} +function findPid { + unset FOUND_PID; + [ $# -eq 1 ] || { + abort "findPid requires a parameter of pattern to match" + return 1 + } + local PATTERN="$1"; shift + local _FOUND=`ps auxwww|grep "$PATTERN"|grep -v " $0"|grep -v grep|grep -v $$|awk '{print $2}'` + [ -n "$_FOUND" ] && { + export FOUND_PID=$_FOUND + return 0 + } || { + return 1 + } +} +function forget { + unset FOUND_PID; + [ $# -eq 3 ] || { + abort "forget requires parameters INSTANCE_NAME SCRIPT LOG_DIR" + return 1 + } + local INSTANCE_NAME="$1"; shift + local SCRIPT="$1"; shift + local LOG_DIR="$1"; shift + mkdir -p $LOG_DIR + findPid $INSTANCE_NAME + [ -n "$FOUND_PID" -a -f $LOG_DIR/stdout.log ] && { + echo $INSTANCE_NAME already running pid $FOUND_PID + return 1; + } || { + nohup $SCRIPT >$LOG_DIR/stdout.log 2>$LOG_DIR/stderr.log & + RETURN=$? + # this is generally followed by findPid, so we shouldn't exit + # immediately as the proc may not have registered in ps, yet + test $RETURN && sleep 1 + return $RETURN; + } +} +export PATH=/usr/ucb/bin:/bin:/sbin:/usr/bin:/usr/sbin +case $1 in +init) + default || exit 1 + install_git || exit 1 + mkdir -p $INSTANCE_HOME + + # create runscript header + cat > $INSTANCE_HOME/install_git.sh <<-'END_OF_JCLOUDS_SCRIPT' + #!/bin/bash + set +u + shopt -s xpg_echo + shopt -s expand_aliases + + PROMPT_COMMAND='echo -ne \"\033]0;install_git\007\"' + export PATH=/usr/ucb/bin:/bin:/sbin:/usr/bin:/usr/sbin + + export INSTANCE_NAME='install_git' +END_OF_JCLOUDS_SCRIPT + cat >> $INSTANCE_HOME/install_git.sh <<-END_OF_JCLOUDS_SCRIPT + export INSTANCE_NAME='$INSTANCE_NAME' + export INSTANCE_HOME='$INSTANCE_HOME' + export LOG_DIR='$LOG_DIR' +END_OF_JCLOUDS_SCRIPT + cat >> $INSTANCE_HOME/install_git.sh <<-'END_OF_JCLOUDS_SCRIPT' + function abort { + echo "aborting: $@" 1>&2 + exit 1 +} +alias apt-get-update="apt-get update -qq" +alias apt-get-install="apt-get install -f -y -qq --force-yes" +alias yum-install="yum --quiet --nogpgcheck -y install" + +function ensure_cmd_or_install_package_apt(){ + local cmd=$1 + local pkg=$2 + + hash $cmd 2>/dev/null || ( apt-get-update && apt-get-install $pkg ) +} + +function ensure_cmd_or_install_package_yum(){ + local cmd=$1 + local pkg=$2 + hash $cmd 2>/dev/null || yum-install $pkg +} + +function ensure_netutils_apt() { + ensure_cmd_or_install_package_apt nslookup dnsutils + ensure_cmd_or_install_package_apt curl curl +} + +function ensure_netutils_yum() { + ensure_cmd_or_install_package_yum nslookup bind-utils + ensure_cmd_or_install_package_yum curl curl +} + +# most network services require that the hostname is in +# the /etc/hosts file, or they won't operate +function ensure_hostname_in_hosts() { + egrep -q `hostname` /etc/hosts || awk -v hostname=`hostname` 'END { print $1" "hostname }' /proc/net/arp >> /etc/hosts +} + +# download locations for many services are at public dns +function ensure_can_resolve_public_dns() { + nslookup yahoo.com > /dev/null || echo nameserver 208.67.222.222 >> /etc/resolv.conf +} + +function setupPublicCurl() { + ensure_hostname_in_hosts + if hash apt-get 2>/dev/null; then + ensure_netutils_apt + elif hash yum 2>/dev/null; then + ensure_netutils_yum + else + abort "we only support apt-get and yum right now... please contribute!" + return 1 + fi + ensure_can_resolve_public_dns + return 0 +} +function installGit() { + if hash apt-get 2>/dev/null; then + ensure_cmd_or_install_package_apt git git-core + elif hash yum 2>/dev/null; then + ensure_cmd_or_install_package_yum git git-core + else + abort "we only support apt-get and yum right now... please contribute!" + return 1 + fi + return 0 +} + +END_OF_JCLOUDS_SCRIPT + + # add desired commands from the user + cat >> $INSTANCE_HOME/install_git.sh <<-'END_OF_JCLOUDS_SCRIPT' + cd $INSTANCE_HOME + rm -f $INSTANCE_HOME/rc + trap 'echo $?>$INSTANCE_HOME/rc' 0 1 2 3 15 + setupPublicCurl || exit 1 + + installGit || exit 1 + +END_OF_JCLOUDS_SCRIPT + + # add runscript footer + cat >> $INSTANCE_HOME/install_git.sh <<-'END_OF_JCLOUDS_SCRIPT' + exit $? + +END_OF_JCLOUDS_SCRIPT + + chmod u+x $INSTANCE_HOME/install_git.sh + ;; +status) + default || exit 1 + findPid $INSTANCE_NAME || exit 1 + echo $FOUND_PID + ;; +stop) + default || exit 1 + findPid $INSTANCE_NAME || exit 1 + [ -n "$FOUND_PID" ] && { + echo stopping $FOUND_PID + kill -9 $FOUND_PID + } + ;; +start) + default || exit 1 + forget $INSTANCE_NAME $INSTANCE_HOME/$INSTANCE_NAME.sh $LOG_DIR || exit 1 + ;; +stdout) + default || exit 1 + cat $LOG_DIR/stdout.log + ;; +stderr) + default || exit 1 + cat $LOG_DIR/stderr.log + ;; +exitstatus) + default || exit 1 + [ -f $LOG_DIR/rc ] && cat $LOG_DIR/rc;; +tail) + default || exit 1 + tail $LOG_DIR/stdout.log + ;; +tailerr) + default || exit 1 + tail $LOG_DIR/stderr.log + ;; +run) + default || exit 1 + $INSTANCE_HOME/$INSTANCE_NAME.sh + ;; +esac +exit $?