Repository: oozie
Updated Branches:
  refs/heads/master 6323a8e43 -> fe2da6e57


http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/sharelib/git/src/main/java/org/apache/oozie/action/hadoop/GitOperations.java
----------------------------------------------------------------------
diff --git 
a/sharelib/git/src/main/java/org/apache/oozie/action/hadoop/GitOperations.java 
b/sharelib/git/src/main/java/org/apache/oozie/action/hadoop/GitOperations.java
new file mode 100644
index 0000000..3505790
--- /dev/null
+++ 
b/sharelib/git/src/main/java/org/apache/oozie/action/hadoop/GitOperations.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.apache.oozie.action.hadoop;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.TransportConfigCallback;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.transport.JschConfigSessionFactory;
+import org.eclipse.jgit.transport.OpenSshConfig;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.SshTransport;
+import org.eclipse.jgit.transport.Transport;
+import org.eclipse.jgit.util.FS;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+class GitOperations {
+    private final URI srcURL;
+    private final String branch;
+    private final File credentialFile;
+
+    GitOperations(final URI gitSrc, final String branch, final File 
credentialFile) {
+       this.srcURL = gitSrc;
+       this.branch = branch;
+       this.credentialFile = credentialFile;
+    }
+
+    /**
+     * Used by GitOperations to wrap a Throwable when an Exception occurs
+     */
+    @SuppressWarnings("serial")
+    static class GitOperationsException extends Exception {
+        GitOperationsException(final String message, Throwable throwable) {
+            super(message, throwable);
+        }
+    }
+
+    /**
+     * Clones a Git repository
+     * @param outputDir location in which to clone the Git repository
+     * @throws GitOperationsException if the Git clone fails
+     */
+    private void cloneRepo(final File outputDir) throws GitOperationsException 
{
+        final SshSessionFactory sshSessionFactory = new 
JschConfigSessionFactory() {
+            @Override
+            protected void configure(final OpenSshConfig.Host host, final 
Session session) {
+                // nop
+            }
+
+            @Override
+            protected JSch createDefaultJSch(final FS fs) throws JSchException 
{
+                JSch.setConfig("StrictHostKeyChecking", "no");
+                final JSch defaultJSch = super.createDefaultJSch(fs);
+
+                if (credentialFile != null) {
+                    defaultJSch.addIdentity(credentialFile.toString());
+                }
+
+                return defaultJSch;
+            }
+        };
+
+        final CloneCommand cloneCommand = Git.cloneRepository();
+        cloneCommand.setURI(srcURL.toString());
+
+        if (srcURL.getScheme().toLowerCase().equals("ssh")) {
+          cloneCommand.setTransportConfigCallback(new 
TransportConfigCallback() {
+              @Override
+              public void configure(final Transport transport) {
+                  final SshTransport sshTransport = (SshTransport)transport;
+                  sshTransport.setSshSessionFactory(sshSessionFactory);
+              }
+          });
+        }
+
+        cloneCommand.setDirectory(outputDir);
+        // set our branch identifier
+        if (branch != null) {
+            cloneCommand.setBranchesToClone(Arrays.asList("refs/heads/" + 
branch));
+        }
+
+        try {
+            cloneCommand.call();
+        } catch (final GitAPIException e) {
+            throw new GitOperationsException("Unable to clone Git repo: ", e);
+        }
+    }
+
+    /**
+     * Clone a Git repo up to a FileSystem
+     *
+     * @param destination - Hadoop FileSystem path to which repository should 
be cloned
+     * @throws GitOperationsException if the Git operations fail
+     * @throws IOException if the HDFS or local file system operations fail
+     */
+    @SuppressFBWarnings(value ="PATH_TRAVERSAL_IN", justification = "Path is 
created runtime")
+    String cloneRepoToFS(final Path destination) throws IOException, 
GitOperationsException {
+        final File tempDir = GitMain.createTempDir("git");
+
+        final Configuration conf = new Configuration();
+        final FileSystem fs = FileSystem.get(destination.toUri(), conf);
+
+        cloneRepo(tempDir);
+
+        // create a list of files and directories to upload
+        final File src = new File(tempDir.getAbsolutePath());
+        final ArrayList<Path> srcs = new ArrayList<Path>(1000);
+        final File[] sourceFiles = src.listFiles();
+        if (sourceFiles != null) {
+            for (final File sourceFile : sourceFiles) {
+                srcs.add(new Path(sourceFile.toString()));
+            }
+        }
+
+        System.out.println("Finished cloning to local");
+
+        fs.mkdirs(destination);
+        fs.copyFromLocalFile(false, true, srcs.toArray(new Path[0]), 
destination);
+
+        System.out.println("Finished copying to " + destination.toString());
+
+        return destination.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/GitServer.java
----------------------------------------------------------------------
diff --git 
a/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/GitServer.java 
b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/GitServer.java
new file mode 100644
index 0000000..03d6e55
--- /dev/null
+++ b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/GitServer.java
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.apache.oozie.action.hadoop;
+
+import org.apache.commons.io.FileUtils;
+
+import org.apache.oozie.util.XLog;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.Daemon;
+import org.eclipse.jgit.transport.DaemonClient;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.resolver.RepositoryResolver;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+class GitServer {
+    private static final XLog LOG = XLog.getLog(GitServer.class);
+
+    /**
+     * A simple git server serving anynymous git: protocol
+     */
+    private final Map<String, Repository> repositories = new HashMap<>();
+    private Daemon server;
+    private final int localPort;
+
+    GitServer() throws IOException {
+        LOG.info("Creating Git server");
+
+        this.localPort = findAvailablePort();
+
+        LOG.info("Git server created, port {0} will be used", this.localPort);
+    }
+
+    void start() throws IOException {
+        LOG.info("Starting Git server on port {0}", this.localPort);
+
+        this.server = new Daemon(new InetSocketAddress(this.localPort));
+        this.server.getService("git-receive-pack").setEnabled(true);
+        this.server.setRepositoryResolver(new 
EmptyRepositoryResolverImplementation());
+        this.server.start();
+
+        LOG.info("Git server started");
+    }
+
+    int getLocalPort() {
+        return localPort;
+    }
+
+    private int findAvailablePort() throws IOException {
+        try (final ServerSocket serverSocket = new ServerSocket(0)) {
+            final int availablePort = serverSocket.getLocalPort();
+            LOG.info("Found available port {0}", availablePort);
+            return availablePort;
+        }
+    }
+
+    void stopAndCleanupReposServer() {
+        cleanUpRepos();
+        this.server.stop();
+    }
+
+    /**
+     * A method to:
+     * <ul>
+     *     <li>remove all files on disk for all repositories</li>
+     *     <li>clear the repositories listed for the {@link GitServer}</li>
+     * </ul>
+     */
+    private void cleanUpRepos() {
+        for (final Repository repository : repositories.values()) {
+            final File workTree = repository.getWorkTree();
+            try {
+                FileUtils.deleteDirectory(workTree.getParentFile());
+            }
+            catch (final IOException e) {
+                LOG.warn("Could not delete parent directory of working tree: 
", e);
+            }
+        }
+        repositories.clear();
+    }
+
+    /**
+     * A simple class RepositoryResolver to provide an empty repository for 
non-existant repo requests
+     */
+    private final class EmptyRepositoryResolverImplementation implements
+            RepositoryResolver<DaemonClient> {
+
+        @Override
+        public Repository open(final DaemonClient client, final String name) {
+            Repository repo = repositories.get(name);
+            if (repo == null) {
+                try {
+                    final Path workDir = 
Files.createTempDirectory("GitTestSetup");
+                    //git init
+                    repo = FileRepositoryBuilder.create(new 
File(workDir.resolve(name).toFile(), ".git"));
+                    repo.create();
+                    // commit into the filesystem
+                    final Git git = new Git(repo);
+                    // one needs an initial commit for a proper clone
+                    addEmptyCommit(git);
+                    git.close();
+                    // serve the Git repo
+                    repositories.put(name, repo);
+                }
+                catch (final Exception e) {
+                    throw new RuntimeException();
+                }
+            }
+            return repo;
+        }
+
+        /**
+         * Add an empty commit to a Git repository
+         * @param git a Git repository to add an empty commit to
+         */
+        void addEmptyCommit(final Git git) {
+            try {
+                git.commit().setMessage("Empty commit").call();
+            }
+            catch (final GitAPIException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitActionExecutor.java
----------------------------------------------------------------------
diff --git 
a/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitActionExecutor.java
 
b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitActionExecutor.java
new file mode 100644
index 0000000..433f7e2
--- /dev/null
+++ 
b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitActionExecutor.java
@@ -0,0 +1,213 @@
+/** * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.apache.oozie.action.hadoop;
+
+import java.io.IOException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.permission.AclEntry;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.oozie.action.ActionExecutorException;
+import org.apache.oozie.service.WorkflowAppService;
+import org.apache.oozie.util.XConfiguration;
+import org.apache.oozie.util.XmlUtils;
+import org.apache.oozie.WorkflowActionBean;
+import org.apache.oozie.WorkflowJobBean;
+import org.jdom.Element;
+import org.junit.Assert;
+
+public class TestGitActionExecutor extends ActionExecutorTestCase {
+
+    @SuppressWarnings("unchecked")
+    public void testWhenParametersFilledThenConfigurationFieldsPopulated() 
throws Exception {
+        final GitActionExecutor ae = new GitActionExecutor();
+        assertTrue("Can not find GitMain class in launcher classes",
+          ae.getLauncherClasses().contains(GitMain.class));
+
+        final Path testKey = new Path(getAppPath().toString() + "/test_key");
+        createTestFile(testKey);
+        final FileSystem fs = getFileSystem();
+        fs.setPermission(testKey, FsPermission.valueOf("-r--------"));
+
+        final String repoUrl = "https://github.com/apache/oozie";;
+        final String keyUrl = testKey.toString();
+        final String destDir = "repoDir";
+        final String branch = "myBranch";
+        final Element actionXml = XmlUtils.parseXml("<git>" +
+                "<resource-manager>" + getJobTrackerUri() + 
"</resource-manager>" +
+                "<name-node>" + getNameNodeUri() + "</name-node>" +
+                "<git-uri>" + repoUrl + "</git-uri>"+
+                "<branch>" + branch + "</branch>"+
+                "<key-path>" + keyUrl + "</key-path>"+
+                "<destination-uri>" + destDir + "</destination-uri>" +
+                "</git>");
+
+        final XConfiguration protoConf = new XConfiguration();
+        protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
+
+        final WorkflowJobBean wf = createBaseWorkflow(protoConf, 
GitActionExecutor.GIT_ACTION_TYPE + "-action");
+        final WorkflowActionBean action = (WorkflowActionBean) 
wf.getActions().get(0);
+        action.setType(ae.getType());
+
+        final Context context = new Context(wf, action);
+        final Configuration conf = ae.createBaseHadoopConf(context, actionXml);
+        ae.setupActionConf(conf, context, actionXml, getFsTestCaseDir());
+
+        assertEquals("git uri must be set", repoUrl, 
conf.get(GitActionExecutor.GIT_URI));
+        assertEquals("key path must be set", keyUrl, 
conf.get(GitActionExecutor.KEY_PATH));
+        assertEquals("branch must be set", branch, 
conf.get(GitActionExecutor.GIT_BRANCH));
+        assertEquals("destination uri must be set", destDir, 
conf.get(GitActionExecutor.DESTINATION_URI));
+    }
+
+    public void testAccessKeyPermissionsInsecure() throws Exception {
+        final GitActionExecutor ae = new GitActionExecutor();
+
+        final Path testKey = new Path(getAppPath().toString() + "/test_key");
+        createTestFile(testKey);
+        final FileSystem fs = getFileSystem();
+        fs.setPermission(testKey, FsPermission.valueOf("-r-----rw-"));
+
+        final String repoUrl = "https://github.com/apache/oozie";;
+        final String keyUrl = testKey.toString();
+        final String destDir = "repoDir";
+        final String branch = "myBranch";
+        final Element actionXml = XmlUtils.parseXml("<git>" +
+                "<resource-manager>" + getJobTrackerUri() + 
"</resource-manager>" +
+                "<name-node>" + getNameNodeUri() + "</name-node>" +
+                "<git-uri>" + repoUrl + "</git-uri>"+
+                "<branch>" + branch + "</branch>"+
+                "<key-path>" + keyUrl + "</key-path>"+
+                "<destination-uri>" + destDir + "</destination-uri>" +
+                "</git>");
+
+        final XConfiguration protoConf = new XConfiguration();
+        protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
+
+        final WorkflowJobBean wf = createBaseWorkflow(protoConf, 
GitActionExecutor.GIT_ACTION_TYPE + "-action");
+        final WorkflowActionBean action = (WorkflowActionBean) 
wf.getActions().get(0);
+        action.setType(ae.getType());
+
+        final Context context = new Context(wf, action);
+        final Configuration conf = ae.createBaseHadoopConf(context, actionXml);
+        try {
+            // we expect this to throw an ActionExecutorException:
+            ae.setupActionConf(conf, context, actionXml, getFsTestCaseDir());
+            Assert.fail("Expected ActionExecutorException");
+        }
+        catch (final ActionExecutorException e) {
+            if (!e.getMessage().contains("insecure")) {
+                Assert.fail("Unexpected exception message: " + e.getMessage());
+            }
+        }
+    }
+
+    public void testAccessKeyACLsSecure() throws Exception {
+        final GitActionExecutor ae = new GitActionExecutor();
+
+        final Path testKey = new Path(getAppPath().toString() + "/test_key");
+        createTestFile(testKey);
+        // set file permissions to be secure -- allowing only the owner to read
+        final FileSystem fs = getFileSystem();
+        fs.setPermission(testKey, FsPermission.valueOf("-r--------"));
+        fs.setAcl(testKey, 
AclEntry.parseAclSpec("user::rwx,user:foo:rw-,group::r--,other::---", true));
+
+        final String repoUrl = "https://github.com/apache/oozie";;
+        final String keyUrl = testKey.toString();
+        final String destDir = "repoDir";
+        final String branch = "myBranch";
+        final Element actionXml = XmlUtils.parseXml("<git>" +
+                "<resource-manager>" + getJobTrackerUri() + 
"</resource-manager>" +
+                "<name-node>" + getNameNodeUri() + "</name-node>" +
+                "<git-uri>" + repoUrl + "</git-uri>"+
+                "<branch>" + branch + "</branch>"+
+                "<key-path>" + keyUrl + "</key-path>"+
+                "<destination-uri>" + destDir + "</destination-uri>" +
+                "</git>");
+
+        final XConfiguration protoConf = new XConfiguration();
+        protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
+
+        final WorkflowJobBean wf = createBaseWorkflow(protoConf, 
GitActionExecutor.GIT_ACTION_TYPE + "-action");
+        final WorkflowActionBean action = (WorkflowActionBean) 
wf.getActions().get(0);
+        action.setType(ae.getType());
+
+        final Context context = new Context(wf, action);
+        final Configuration conf = ae.createBaseHadoopConf(context, actionXml);
+        try {
+            ae.setupActionConf(conf, context, actionXml, getFsTestCaseDir());
+        }
+        catch (final ActionExecutorException e) {
+            fail("Unexpected exception, could not check ACLs: " + 
e.getMessage());
+        }
+    }
+
+    public void testAccessKeyACLsInsecure() throws Exception {
+        final GitActionExecutor ae = new GitActionExecutor();
+
+        final Path testKey = new Path(getAppPath().toString() + "/test_key");
+        createTestFile(testKey);
+        // set file permissions to be secure -- allowing only the owner to read
+        final FileSystem fs = getFileSystem();
+        fs.setPermission(testKey, FsPermission.valueOf("-r--------"));
+        fs.setAcl(testKey, 
AclEntry.parseAclSpec("user::rwx,user:foo:rw-,group::r--,other::r--", true));
+
+        final String repoUrl = "https://github.com/apache/oozie";;
+        final String keyUrl = testKey.toString();
+        final String destDir = "repoDir";
+        final String branch = "myBranch";
+        final Element actionXml = XmlUtils.parseXml("<git>" +
+                "<resource-manager>" + getJobTrackerUri() + 
"</resource-manager>" +
+                "<name-node>" + getNameNodeUri() + "</name-node>" +
+                "<git-uri>" + repoUrl + "</git-uri>"+
+                "<branch>" + branch + "</branch>"+
+                "<key-path>" + keyUrl + "</key-path>"+
+                "<destination-uri>" + destDir + "</destination-uri>" +
+                "</git>");
+
+        final XConfiguration protoConf = new XConfiguration();
+        protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
+
+        final WorkflowJobBean wf = createBaseWorkflow(protoConf, 
GitActionExecutor.GIT_ACTION_TYPE + "-action");
+        final WorkflowActionBean action = (WorkflowActionBean) 
wf.getActions().get(0);
+        action.setType(ae.getType());
+
+        final Context context = new Context(wf, action);
+
+        try {
+            ae.createBaseHadoopConf(context, actionXml);
+        }
+        catch (final Exception e) {
+            fail("Unexpected exception, could not create Hadoop configuration 
with insecure setup: " + e.getMessage());
+        }
+    }
+
+    private void createTestFile(final Path testFile) throws IOException {
+        final FileSystem fs = getFileSystem();
+        final FSDataOutputStream file = fs.create(testFile);
+        file.writeUTF("");
+        file.close();
+    }
+
+    @Override
+    protected void setSystemProps() throws Exception {
+        super.setSystemProps();
+        setSystemProperty("oozie.service.ActionService.executor.classes", 
GitActionExecutor.class.getName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitMain.java
----------------------------------------------------------------------
diff --git 
a/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitMain.java 
b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitMain.java
new file mode 100644
index 0000000..3b236d4
--- /dev/null
+++ b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestGitMain.java
@@ -0,0 +1,119 @@
+/** * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.apache.oozie.action.hadoop;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.Path;
+import org.apache.oozie.client.OozieClient;
+import org.apache.oozie.client.WorkflowJob;
+import org.apache.oozie.fluentjob.api.action.GitAction;
+import org.apache.oozie.fluentjob.api.action.GitActionBuilder;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+import org.apache.oozie.test.MiniOozieTestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Properties;
+
+public class TestGitMain extends MiniOozieTestCase {
+
+    private GitMain gitMain;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        gitMain = new GitMain();
+        gitMain.setNameNode(getFileSystem().getUri().toString());
+    }
+
+    public void testGitRepoMustHaveScheme() throws Exception {
+        final OozieClient client = this.getClient();
+        final Properties conf = client.createConfiguration();
+
+        final class GitWorkflowFactory implements WorkflowFactory {
+           @Override
+           public Workflow create() {
+               final GitAction gitAction = GitActionBuilder.create()
+                       .withName("git-action")
+                       .withResourceManager(getJobTrackerUri())
+                       .withNameNode(getNameNodeUri())
+                       .withConfigProperty("mapred.job.queue.name", "default")
+                       //Note: no URI scheme
+                       .withGitUri("aURLWithoutAScheme/andSomeMorePathStuff")
+                       .withDestinationUri("repoDir")
+                       .build();
+
+               final Workflow gitWorkflow = new WorkflowBuilder()
+                       .withName("git-workflow")
+                       .withDagContainingNode(gitAction).build();
+
+               return gitWorkflow;
+           }
+        }
+        final Path workflowUri = new Path(getFsTestCaseDir().toString(), 
"workflow.xml");
+        conf.setProperty(OozieClient.APP_PATH, workflowUri.toString());
+
+        writeHDFSFile(workflowUri, new GitWorkflowFactory().create().asXml());
+
+        final String jobId = client.run(conf);
+
+        waitFor(60_000, new Predicate() {
+            @Override
+            public boolean evaluate() throws Exception {
+                return client.getJobInfo(jobId).getStatus() != 
WorkflowJob.Status.RUNNING;
+            }
+        });
+
+        // Should fail with something akin to:
+        // Failing Oozie Launcher, Action Configuration does not have a proper 
URI repoDir null scheme.
+        // org.apache.oozie.action.hadoop.GitMain$GitMainException: Action 
Configuration does not have
+        // a proper URI repoDir null scheme.
+        // One needs to look into the YARN logs though to see this as 
client.getJobLog() is not implemented
+        assertEquals("Git action should have been killed",
+                WorkflowJob.Status.KILLED,
+                client.getJobInfo(jobId).getStatus());
+    }
+
+    public void testGitKeyFileIsCopiedToHDFS() throws Exception {
+        final Path credentialFilePath = Path.mergePaths(getFsTestCaseDir(), 
new Path("/key_dir/my_key.dsa"));
+        final String credentialFileData = "Key file data";
+        Path.mergePaths(getFsTestCaseDir(), new Path("/destDir"));
+
+        writeHDFSFile(credentialFilePath, credentialFileData);
+
+        final File localFile = gitMain.getKeyFromFS(credentialFilePath);
+        final String testOutput = new 
String(Files.readAllBytes(localFile.toPath()));
+
+        assertTrue("credential file length mismatch", 
credentialFileData.length() > 0);
+        assertEquals("credential file data mismatch", credentialFileData, 
testOutput);
+
+        FileUtils.deleteDirectory(new File(localFile.getParent()));
+    }
+
+    private void writeHDFSFile(final Path hdfsFilePath, final String fileData) 
throws IOException {
+        try (final FSDataOutputStream hdfsFileOS = 
getFileSystem().create(hdfsFilePath)) {
+            hdfsFileOS.write(fileData.getBytes());
+            hdfsFileOS.flush();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestIntegrationGitActionExecutor.java
----------------------------------------------------------------------
diff --git 
a/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestIntegrationGitActionExecutor.java
 
b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestIntegrationGitActionExecutor.java
new file mode 100644
index 0000000..20368da
--- /dev/null
+++ 
b/sharelib/git/src/test/java/org/apache/oozie/action/hadoop/TestIntegrationGitActionExecutor.java
@@ -0,0 +1,133 @@
+
+/** * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.apache.oozie.action.hadoop;
+
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.commons.io.IOUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.oozie.WorkflowActionBean;
+import org.apache.oozie.WorkflowJobBean;
+import org.apache.oozie.client.WorkflowAction;
+import org.apache.oozie.service.WorkflowAppService;
+import org.apache.oozie.util.XConfiguration;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+public class TestIntegrationGitActionExecutor extends ActionExecutorTestCase{
+
+    @Override
+    protected void setSystemProps() throws Exception {
+        super.setSystemProps();
+        setSystemProperty("oozie.service.ActionService.executor.classes", 
GitActionExecutor.class.getName());
+    }
+
+    public void testWhenRepoIsClonedThenGitIndexContentIsReadSuccessfully() 
throws Exception {
+        final Path outputPath = getFsTestCaseDir();
+        final Path gitRepo = Path.mergePaths(outputPath, new Path("/repoDir"));
+        final Path gitIndex = Path.mergePaths(gitRepo, new 
Path("/.git/config"));
+
+        final GitServer gitServer = new GitServer();
+
+        final String localRepo = String.format("git://127.0.0.1:%s/repo.git", 
gitServer.getLocalPort());
+        final String actionXml = "<git>" +
+                "<resource-manager>" + getJobTrackerUri() + 
"</resource-manager>" +
+                "<name-node>" + getNameNodeUri() + "</name-node>" +
+                "<git-uri>" + localRepo + "</git-uri>"+
+                "<destination-uri>" + gitRepo + "</destination-uri>" +
+                "</git>";
+
+        final Context context = createContext(actionXml);
+        final String launcherId = submitAction(context);
+
+        try {
+            gitServer.start();
+
+            waitUntilYarnAppDoneAndAssertSuccess(launcherId);
+        }
+        finally {
+            gitServer.stopAndCleanupReposServer();
+        }
+        final Map<String, String> actionData = 
LauncherHelper.getActionData(getFileSystem(), context.getActionDir(),
+                context.getProtoActionConf());
+        assertFalse(LauncherHelper.hasIdSwap(actionData));
+
+        final GitActionExecutor ae = new GitActionExecutor();
+        ae.check(context, context.getAction());
+        assertEquals("launcherId and action.externalId should be the same", 
launcherId, context.getAction().getExternalId());
+        assertEquals("action should have been SUCCEEDED", "SUCCEEDED", 
context.getAction().getExternalStatus());
+
+        ae.end(context, context.getAction());
+        assertEquals("action.status should be OK", WorkflowAction.Status.OK, 
context.getAction().getStatus());
+
+        assertTrue("could not create test case output path", 
getFileSystem().exists(outputPath));
+        assertTrue("could not save git index", 
getFileSystem().exists(gitIndex));
+
+        try (final InputStream is = getFileSystem().open(gitIndex)) {
+            final String gitIndexContent = IOUtils.toString(is, 
StandardCharsets.UTF_8);
+
+            assertTrue("could not read git index", 
gitIndexContent.toLowerCase().contains("core"));
+            assertTrue("could not read git index", 
gitIndexContent.toLowerCase().contains("remote"));
+        }
+    }
+
+    private Context createContext(final String actionXml) throws Exception {
+        final GitActionExecutor ae = new GitActionExecutor();
+
+        final XConfiguration protoConf = new XConfiguration();
+        protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
+
+        final FileSystem fs = getFileSystem();
+        SharelibUtils.addToDistributedCache(GitActionExecutor.GIT_ACTION_TYPE, 
fs, getFsTestCaseDir(), protoConf);
+
+        final WorkflowJobBean wf = createBaseWorkflow(protoConf, 
GitActionExecutor.GIT_ACTION_TYPE + "-action");
+        final WorkflowActionBean action = (WorkflowActionBean) 
wf.getActions().get(0);
+        action.setType(ae.getType());
+        action.setConf(actionXml);
+
+        return new Context(wf, action);
+    }
+
+    private String submitAction(final Context context) throws Exception {
+        final GitActionExecutor ae = new GitActionExecutor();
+
+        final WorkflowAction action = context.getAction();
+
+        ae.prepareActionDir(getFileSystem(), context);
+        ae.submitLauncher(getFileSystem(), context, action);
+
+        final String externalId = action.getExternalId();
+        final String trackerUri = action.getTrackerUri();
+        final String consoleUrl = action.getConsoleUrl();
+        assertNotNull("action.externalId should be filled", externalId);
+        assertNotNull("action.trackerUri should be filled", trackerUri);
+        assertNotNull("action.consoleUrl should be filled", consoleUrl);
+
+        final Configuration conf = createJobConf();
+
+        final String runningJobExternalId = 
context.getAction().getExternalId();
+
+        assertNotNull("running job has a valid externalId", 
runningJobExternalId);
+
+        return runningJobExternalId;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/sharelib/pom.xml
----------------------------------------------------------------------
diff --git a/sharelib/pom.xml b/sharelib/pom.xml
index 39cea25..1c51bfe 100644
--- a/sharelib/pom.xml
+++ b/sharelib/pom.xml
@@ -35,6 +35,7 @@
         <module>streaming</module>
         <module>hcatalog</module>
         <module>pig</module>
+        <module>git</module>
         <module>hive</module>
         <module>hive2</module>
         <module>sqoop</module>

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/src/main/assemblies/sharelib.xml
----------------------------------------------------------------------
diff --git a/src/main/assemblies/sharelib.xml b/src/main/assemblies/sharelib.xml
index 07dc69c..8ce558c 100644
--- a/src/main/assemblies/sharelib.xml
+++ b/src/main/assemblies/sharelib.xml
@@ -66,6 +66,10 @@
             <directory>${basedir}/spark/target/partial-sharelib</directory>
             <outputDirectory>/</outputDirectory>
         </fileSet>
+        <fileSet>
+            <directory>${basedir}/git/target/partial-sharelib</directory>
+            <outputDirectory>/</outputDirectory>
+        </fileSet>
     </fileSets>
 
 </assembly>

http://git-wip-us.apache.org/repos/asf/oozie/blob/fe2da6e5/webapp/pom.xml
----------------------------------------------------------------------
diff --git a/webapp/pom.xml b/webapp/pom.xml
index fd3f89f..938b0c9 100644
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -229,6 +229,10 @@
                                     <groupId>org.apache.oozie</groupId>
                                     
<artifactId>oozie-sharelib-spark</artifactId>
                                 </artifactItem>
+                                <artifactItem>
+                                    <groupId>org.apache.oozie</groupId>
+                                    <artifactId>oozie-sharelib-git</artifactId>
+                                </artifactItem>
                             </artifactItems>
                             
<outputDirectory>${project.build.directory}/oozie-webapp-${project.version}/WEB-INF/lib</outputDirectory>
                             <overWriteIfNewer>true</overWriteIfNewer>

Reply via email to