diff --git a/src/AppHarbor/AccessTokenHelper.cs b/src/AppHarbor/AccessTokenHelper.cs
new file mode 100644
index 0000000..4f72d49
--- /dev/null
+++ b/src/AppHarbor/AccessTokenHelper.cs
@@ -0,0 +1,34 @@
+using RestSharp;
+using RestSharp.Contrib;
+
+namespace AppHarbor
+{
+ class AccessTokenHelper
+ {
+ ///
+ /// Get access token.
+ ///
+ ///
+ ///
+ ///
+ public static string GetAccessToken(string username, string password)
+ {
+ //NOTE: Remove when merged into AppHarbor.NET library
+ var restClient = new RestClient("https://appharbor-token-client.apphb.com");
+ var request = new RestRequest("/token", Method.POST);
+
+ request.AddParameter("username", username);
+ request.AddParameter("password", password);
+
+ var response = restClient.Execute(request);
+ var accessToken = HttpUtility.ParseQueryString(response.Content)["access_token"];
+
+ if (accessToken == null)
+ {
+ throw new CommandException("Couldn't log in. Try again");
+ }
+
+ return accessToken;
+ }
+ }
+}
diff --git a/src/AppHarbor/AppHarbor.csproj b/src/AppHarbor/AppHarbor.csproj
index 3009e1a..fbc6616 100644
--- a/src/AppHarbor/AppHarbor.csproj
+++ b/src/AppHarbor/AppHarbor.csproj
@@ -70,6 +70,7 @@
+
@@ -85,6 +86,7 @@
+
@@ -102,6 +104,7 @@
+
@@ -121,8 +124,10 @@
+
+
diff --git a/src/AppHarbor/AppHarborInstaller.cs b/src/AppHarbor/AppHarborInstaller.cs
index d88e578..d84b60f 100644
--- a/src/AppHarbor/AppHarborInstaller.cs
+++ b/src/AppHarbor/AppHarborInstaller.cs
@@ -84,6 +84,26 @@ public void Install(IWindsorContainer container, IConfigurationStore store)
container.Register(Component
.For()
.ImplementedBy());
+
+ RegisterProgressBar(container);
+ }
+
+ private static void RegisterProgressBar(IWindsorContainer container)
+ {
+ if (ConsoleWindowHelper.HasConsoleWindow)
+ {
+ container.Register(Component
+ .For()
+ .ImplementedBy()
+ .LifeStyle.Transient);
+ }
+ else
+ {
+ container.Register(Component
+ .For()
+ .ImplementedBy()
+ .LifeStyle.Transient);
+ }
}
}
}
diff --git a/src/AppHarbor/Commands/CIDeployAppCommand.cs b/src/AppHarbor/Commands/CIDeployAppCommand.cs
new file mode 100644
index 0000000..e3be599
--- /dev/null
+++ b/src/AppHarbor/Commands/CIDeployAppCommand.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Net;
+using Amazon.S3;
+using Amazon.S3.Transfer;
+using RestSharp;
+
+namespace AppHarbor.Commands
+{
+ [CommandHelp("Deploy application in CI environment", alias: "ci-deploy")]
+ public class CIDeployAppCommand : ApplicationCommand
+ {
+ private string _message;
+ private DirectoryInfo _sourceDirectory;
+ private string _username;
+ private string _password;
+
+ private readonly IRestClient _restClient;
+ private readonly TextWriter _writer;
+ private readonly IProgressBar _progressBar;
+
+ private readonly IList _excludedDirectories;
+
+ public CIDeployAppCommand(IApplicationConfiguration applicationConfiguration, TextWriter writer, IProgressBar progressBar)
+ : base(applicationConfiguration)
+ {
+ _restClient = new RestClient("https://packageclient.apphb.com/");
+ _writer = writer;
+ _progressBar = progressBar;
+
+ _sourceDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
+ OptionSet.Add("source-directory=", "Set source directory", x => _sourceDirectory = new DirectoryInfo(x));
+
+ _excludedDirectories = new List { ".git", ".hg" };
+ OptionSet.Add("e|excluded-directory=", "Add excluded directory name", x => _excludedDirectories.Add(x));
+
+ OptionSet.Add("m|message=", "Specify commit message", x => _message = x);
+
+ OptionSet.Add("u|user=", "Optional. Specify the user to use", x => _username = x);
+ OptionSet.Add("p|password=", "Optional. Specify the password of the user", x => _password = x);
+ }
+
+ protected override void InnerExecute(string[] arguments)
+ {
+ _writer.WriteLine("Ensure login credentials...");
+ string accessToken = GetAccessToken();
+ _writer.WriteLine();
+
+ _writer.WriteLine("Getting upload credentials... ");
+ _writer.WriteLine();
+
+ var uploadCredentials = GetCredentials();
+
+ var temporaryFileName = Path.GetTempFileName();
+ try
+ {
+ using (var packageStream = new FileStream(temporaryFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
+ using (var gzipStream = new GZipStream(packageStream, CompressionMode.Compress, true))
+ {
+ _sourceDirectory.ToTar(gzipStream, excludedDirectoryNames: _excludedDirectories.ToArray(), progressBar: _progressBar);
+ }
+
+ using (var s3Client = new AmazonS3Client(uploadCredentials.GetSessionCredentials()))
+ using (var transferUtility = new TransferUtility(s3Client))
+ {
+ var request = new TransferUtilityUploadRequest
+ {
+ FilePath = temporaryFileName,
+ BucketName = uploadCredentials.Bucket,
+ Key = uploadCredentials.ObjectKey,
+ Timeout = (int)TimeSpan.FromHours(2).TotalMilliseconds,
+ };
+
+ request.UploadProgressEvent += (object x, UploadProgressArgs y) => _progressBar
+ .Update("Uploading package", y.TransferredBytes, y.TotalBytes);
+
+ transferUtility.Upload(request);
+
+ Console.CursorTop++;
+ _writer.WriteLine();
+ }
+ }
+ finally
+ {
+ File.Delete(temporaryFileName);
+ }
+
+ TriggerAppHarborBuild(uploadCredentials, accessToken);
+ }
+
+ private FederatedUploadCredentials GetCredentials()
+ {
+ var urlRequest = new RestRequest("applications/{slug}/uploadCredentials", Method.POST);
+ urlRequest.AddUrlSegment("slug", ApplicationId);
+
+ var federatedCredentials = _restClient.Execute(urlRequest);
+ return federatedCredentials.Data;
+ }
+
+ private void TriggerAppHarborBuild(FederatedUploadCredentials credentials, string accessToken)
+ {
+ _writer.WriteLine("The package will be deployed to application \"{0}\".", ApplicationId);
+
+ if (string.IsNullOrEmpty(_message))
+ {
+ _message = string.Format("CI Deployment at {0}", DateTime.Now);
+ }
+
+ var request = new RestRequest("applications/{slug}/buildnotifications", Method.POST)
+ {
+ RequestFormat = DataFormat.Json
+ }
+ .AddUrlSegment("slug", ApplicationId)
+ .AddHeader("Authorization", string.Format("BEARER {0}", accessToken))
+ .AddBody(new
+ {
+ Bucket = credentials.Bucket,
+ ObjectKey = credentials.ObjectKey,
+ CommitMessage = string.IsNullOrEmpty(_message) ? "Deployed from CLI" : _message,
+ });
+
+ _writer.WriteLine("Notifying AppHarbor.");
+
+ var response = _restClient.Execute(request);
+
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ using (new ForegroundColor(ConsoleColor.Green))
+ {
+ _writer.WriteLine("Deploying... Open application overview with `appharbor open`.");
+ }
+ }
+ }
+
+ private string GetAccessToken()
+ {
+ // Request new access token using the specific
+ string accessToken = AccessTokenHelper.GetAccessToken(_username, _password);
+ _writer.WriteLine("Logged in with the username " + _username);
+
+ return accessToken;
+ }
+ }
+}
diff --git a/src/AppHarbor/Commands/DeployAppCommand.cs b/src/AppHarbor/Commands/DeployAppCommand.cs
index fa5cf3e..616558b 100644
--- a/src/AppHarbor/Commands/DeployAppCommand.cs
+++ b/src/AppHarbor/Commands/DeployAppCommand.cs
@@ -20,16 +20,18 @@ public class DeployAppCommand : ApplicationCommand
private readonly IRestClient _restClient;
private readonly TextReader _reader;
private readonly TextWriter _writer;
+ private readonly IProgressBar _progressBar;
private readonly IList _excludedDirectories;
- public DeployAppCommand(IApplicationConfiguration applicationConfiguration, IAccessTokenConfiguration accessTokenConfiguration, TextReader reader, TextWriter writer)
+ public DeployAppCommand(IApplicationConfiguration applicationConfiguration, IAccessTokenConfiguration accessTokenConfiguration, TextReader reader, TextWriter writer, IProgressBar progressBar)
: base(applicationConfiguration)
{
_accessToken = accessTokenConfiguration.GetAccessToken();
_restClient = new RestClient("https://packageclient.apphb.com/");
_reader = reader;
_writer = writer;
+ _progressBar = progressBar;
_sourceDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
OptionSet.Add("source-directory=", "Set source directory", x => _sourceDirectory = new DirectoryInfo(x));
@@ -53,7 +55,7 @@ protected override void InnerExecute(string[] arguments)
using (var packageStream = new FileStream(temporaryFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
using (var gzipStream = new GZipStream(packageStream, CompressionMode.Compress, true))
{
- _sourceDirectory.ToTar(gzipStream, excludedDirectoryNames: _excludedDirectories.ToArray());
+ _sourceDirectory.ToTar(gzipStream, excludedDirectoryNames: _excludedDirectories.ToArray(), progressBar: _progressBar);
}
using (var s3Client = new AmazonS3Client(uploadCredentials.GetSessionCredentials()))
@@ -67,8 +69,7 @@ protected override void InnerExecute(string[] arguments)
Timeout = (int)TimeSpan.FromHours(2).TotalMilliseconds,
};
- var progressBar = new MegaByteProgressBar();
- request.UploadProgressEvent += (object x, UploadProgressArgs y) => progressBar
+ request.UploadProgressEvent += (object x, UploadProgressArgs y) => _progressBar
.Update("Uploading package", y.TransferredBytes, y.TotalBytes);
transferUtility.Upload(request);
diff --git a/src/AppHarbor/Commands/LoginUserCommand.cs b/src/AppHarbor/Commands/LoginUserCommand.cs
index 2da2fdc..2f66dcf 100644
--- a/src/AppHarbor/Commands/LoginUserCommand.cs
+++ b/src/AppHarbor/Commands/LoginUserCommand.cs
@@ -41,22 +41,7 @@ protected override void InnerExecute(string[] arguments)
public virtual string GetAccessToken(string username, string password)
{
- //NOTE: Remove when merged into AppHarbor.NET library
- var restClient = new RestClient("https://appharbor-token-client.apphb.com");
- var request = new RestRequest("/token", Method.POST);
-
- request.AddParameter("username", username);
- request.AddParameter("password", password);
-
- var response = restClient.Execute(request);
- var accessToken = HttpUtility.ParseQueryString(response.Content)["access_token"];
-
- if (accessToken == null)
- {
- throw new CommandException("Couldn't log in. Try again");
- }
-
- return accessToken;
+ return AccessTokenHelper.GetAccessToken(username, password);
}
}
}
diff --git a/src/AppHarbor/CompressionExtensions.cs b/src/AppHarbor/CompressionExtensions.cs
index b7b8e84..37a42d6 100644
--- a/src/AppHarbor/CompressionExtensions.cs
+++ b/src/AppHarbor/CompressionExtensions.cs
@@ -7,7 +7,7 @@ namespace AppHarbor
{
public static class CompressionExtensions
{
- public static void ToTar(this DirectoryInfo sourceDirectory, Stream output, string[] excludedDirectoryNames)
+ public static void ToTar(this DirectoryInfo sourceDirectory, Stream output, string[] excludedDirectoryNames, IProgressBar progressBar)
{
var archive = TarArchive.CreateOutputTarArchive(output);
@@ -19,7 +19,6 @@ public static void ToTar(this DirectoryInfo sourceDirectory, Stream output, stri
var entriesCount = entries.Count();
- var progressBar = new MegaByteProgressBar();
for (var i = 0; i < entriesCount; i++)
{
archive.WriteEntry(entries[i], true);
diff --git a/src/AppHarbor/ConsoleProgressBar.cs b/src/AppHarbor/ConsoleProgressBar.cs
index 01a44cb..3eec3bf 100644
--- a/src/AppHarbor/ConsoleProgressBar.cs
+++ b/src/AppHarbor/ConsoleProgressBar.cs
@@ -4,7 +4,7 @@
namespace AppHarbor
{
- public abstract class ConsoleProgressBar
+ public abstract class ConsoleProgressBar : IProgressBar
{
private const char ProgressBarCharacter = '\u2592';
diff --git a/src/AppHarbor/ConsoleWindowHelper.cs b/src/AppHarbor/ConsoleWindowHelper.cs
new file mode 100644
index 0000000..9ce9978
--- /dev/null
+++ b/src/AppHarbor/ConsoleWindowHelper.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace AppHarbor
+{
+ ///
+ /// Helper class for console window information.
+ ///
+ static class ConsoleWindowHelper
+ {
+ ///
+ /// Get indication if a console window is available or we run
+ /// without a window (in CI environment).
+ ///
+ public static bool HasConsoleWindow
+ {
+ get
+ {
+ bool hasConsoleWindow;
+ try
+ {
+ int w = Console.BufferWidth;
+ hasConsoleWindow = true;
+ }
+ catch (Exception)
+ {
+ hasConsoleWindow = false;
+ }
+ return hasConsoleWindow;
+ }
+ }
+ }
+}
diff --git a/src/AppHarbor/IProgressBar.cs b/src/AppHarbor/IProgressBar.cs
new file mode 100644
index 0000000..1243aa2
--- /dev/null
+++ b/src/AppHarbor/IProgressBar.cs
@@ -0,0 +1,16 @@
+namespace AppHarbor
+{
+ ///
+ /// Represent a progress bar that show status updates.
+ ///
+ public interface IProgressBar
+ {
+ ///
+ /// Show update on the progress.
+ ///
+ ///
+ ///
+ ///
+ void Update(string message, long processedItems, long totalItems);
+ }
+}
diff --git a/src/AppHarbor/NullProgressBar.cs b/src/AppHarbor/NullProgressBar.cs
new file mode 100644
index 0000000..fd7b20a
--- /dev/null
+++ b/src/AppHarbor/NullProgressBar.cs
@@ -0,0 +1,13 @@
+
+namespace AppHarbor
+{
+ ///
+ /// Reperesent a null progress bar which does nothing.
+ ///
+ public class NullProgressBar : IProgressBar
+ {
+ public void Update(string message, long processedItems, long totalItems)
+ {
+ }
+ }
+}
\ No newline at end of file