Skip to content
This repository has been archived by the owner on May 10, 2023. It is now read-only.

Add connectivity test buttons to Jira config page #18

Merged
merged 9 commits into from
Aug 20, 2019
4 changes: 3 additions & 1 deletion source/Server/Configuration/JiraConfigurationResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ public class JiraConfigurationResource : ExtensionConfigurationResource
public string BaseUrl { get; set; }

[DisplayName("Jira Connect App Password")]
[Description("Set the password for authenticating with the Jira Connect App (generated by the Jira Connect application upon installation in to your Jira instance). Once set deployment data will be sent to Jira.")]
[Description("Set the password for authenticating with Jira Connect App to allow deployment data to be sent to Jira. [Learn more](https://g.octopushq.com/JiraIssueTracker#connecting-jira-cloud-and-octopus).")]
[Writeable]
[ApplicableWhenSpecificValue(nameof(JiraInstanceType), "Cloud")]
[AllowConnectivityCheck("Jira Connect App configuration", JiraIssueTrackerApi.ApiConnectAppCredentialsTest, nameof(BaseUrl), nameof(Password))]
public SensitiveValue Password { get; set; }

[DisplayName("Octopus Installation Id")]
Expand Down Expand Up @@ -58,6 +59,7 @@ public class ReleaseNoteOptionsResource
[DisplayName("Jira Password")]
[Description(PasswordDescription)]
[Writeable]
[AllowConnectivityCheck("Jira credentials", JiraIssueTrackerApi.ApiJiraCredentialsTest, nameof(JiraConfigurationResource.BaseUrl), nameof(Username), nameof(Password))]
public SensitiveValue Password { get; set; }

[DisplayName("Release Note Prefix")]
Expand Down
34 changes: 5 additions & 29 deletions source/Server/Deployments/DeploymentObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Octopus.Server.Extensibility.HostServices.Model.Projects;
using Octopus.Server.Extensibility.IssueTracker.Jira.Configuration;
using Octopus.Server.Extensibility.IssueTracker.Jira.Environments;
using Octopus.Server.Extensibility.IssueTracker.Jira.Integration;
using Octopus.Time;

namespace Octopus.Server.Extensibility.IssueTracker.Jira.Deployments
Expand All @@ -24,6 +25,7 @@ public class DeploymentObserver : IObserveDomainEvent<DeploymentEvent>
{
private readonly ILogWithContext log;
private readonly IJiraConfigurationStore store;
private readonly JiraConnectAppClient connectAppClient;
private readonly IInstallationIdProvider installationIdProvider;
private readonly IClock clock;
private readonly IProvideDeploymentEnvironmentSettingsValues deploymentEnvironmentSettingsProvider;
Expand All @@ -35,6 +37,7 @@ public class DeploymentObserver : IObserveDomainEvent<DeploymentEvent>

public DeploymentObserver(ILogWithContext log,
IJiraConfigurationStore store,
JiraConnectAppClient connectAppClient,
IInstallationIdProvider installationIdProvider,
IClock clock,
IProvideDeploymentEnvironmentSettingsValues deploymentEnvironmentSettingsProvider,
Expand All @@ -46,6 +49,7 @@ public DeploymentObserver(ILogWithContext log,
{
this.log = log;
this.store = store;
this.connectAppClient = connectAppClient;
this.installationIdProvider = installationIdProvider;
this.clock = clock;
this.deploymentEnvironmentSettingsProvider = deploymentEnvironmentSettingsProvider;
Expand Down Expand Up @@ -75,7 +79,7 @@ public void Handle(DeploymentEvent domainEvent)
}

// get token from connect App
var token = GetAuthTokenFromConnectApp();
var token = connectAppClient.GetAuthTokenFromConnectApp();
if (token is null)
{
log.Finish();
Expand All @@ -89,34 +93,6 @@ public void Handle(DeploymentEvent domainEvent)
}
}

string GetAuthTokenFromConnectApp()
{
using (var client = new HttpClient())
{
var username = installationIdProvider.GetInstallationId();
var password = store.GetConnectAppPassword();
var encodedAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedAuth);
var result = client.GetAsync($"{store.GetConnectAppUrl()}/token").GetAwaiter().GetResult();

if (result.IsSuccessStatusCode)
{
var authTokenFromConnectApp = JsonConvert.DeserializeObject<JsonTokenData>(result.Content.ReadAsStringAsync().GetAwaiter().GetResult());
return authTokenFromConnectApp.Token;
}

log.ErrorFormat("Unable to get authentication token for Jira Connect App. Response code: {0}", result.StatusCode);
return null;
}
}

class JsonTokenData
{
[JsonProperty("token")]
public string Token { get; set; }
}

void PublishToJira(string token, DeploymentEventType eventType, IDeployment deployment)
{
var envSettings =
Expand Down
2 changes: 2 additions & 0 deletions source/Server/Integration/IJiraRestClient.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Octopus.Server.Extensibility.IssueTracker.Jira.Web.Response;

namespace Octopus.Server.Extensibility.IssueTracker.Jira.Integration
{
public interface IJiraRestClient
{
Task<ConnectivityCheckResponse> GetServerInfo();
Task<JiraIssue> GetIssue(string workItemId);
Task<JiraIssueComments> GetIssueComments(string workItemId);
}
Expand Down
62 changes: 62 additions & 0 deletions source/Server/Integration/JiraConnectAppClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
using Octopus.Diagnostics;
using Octopus.Server.Extensibility.HostServices.Licensing;
using Octopus.Server.Extensibility.IssueTracker.Jira.Configuration;

namespace Octopus.Server.Extensibility.IssueTracker.Jira.Integration
{
public class JiraConnectAppClient
{
private readonly ILogWithContext log;
private readonly IInstallationIdProvider installationIdProvider;
private readonly IJiraConfigurationStore configurationStore;

public JiraConnectAppClient(
ILogWithContext log,
IInstallationIdProvider installationIdProvider,
IJiraConfigurationStore configurationStore)
{
this.log = log;
this.installationIdProvider = installationIdProvider;
this.configurationStore = configurationStore;
}

public string GetAuthTokenFromConnectApp()
{
var username = installationIdProvider.GetInstallationId().ToString();
var password = configurationStore.GetConnectAppPassword();
return GetAuthTokenFromConnectApp(username, password);
}

public string GetAuthTokenFromConnectApp(string username, string password)
{
using (var client = new HttpClient())
{
var encodedAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedAuth);
var result = client.GetAsync($"{configurationStore.GetConnectAppUrl()}/token").GetAwaiter().GetResult();

if (result.IsSuccessStatusCode)
{
var authTokenFromConnectApp = JsonConvert.DeserializeObject<JsonTokenData>(result.Content.ReadAsStringAsync().GetAwaiter().GetResult());
return authTokenFromConnectApp.Token;
}

log.ErrorFormat("Unable to get authentication token for Jira Connect App. Response code: {0}", result.StatusCode);
return null;
}
}

class JsonTokenData
{
[JsonProperty("token")]
public string Token { get; set; }
}

}
}
20 changes: 18 additions & 2 deletions source/Server/Integration/JiraRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using Octopus.Diagnostics;
using Octopus.Server.Extensibility.IssueTracker.Jira.Web.Response;

namespace Octopus.Server.Extensibility.IssueTracker.Jira.Integration
{
Expand All @@ -26,6 +27,21 @@ public JiraRestClient(string baseUrl, string username, string password, ILog log
AuthorizationHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")));
}

public async Task<ConnectivityCheckResponse> GetServerInfo()
{
using (var client = CreateHttpClient())
{
var response = await client.GetAsync($"{baseUrl}/{baseApiUri}/serverInfo");
if (response.IsSuccessStatusCode)
{
return ConnectivityCheckResponse.Success;
}

return ConnectivityCheckResponse.Failure(
$"Failed to connect to {baseUrl}. Response code: {response.StatusCode}{(!string.IsNullOrEmpty(response.ReasonPhrase) ? $"Reason: {response.ReasonPhrase}" : "")}");
}
}

public async Task<JiraIssue> GetIssue(string workItemId)
{
using (var client = CreateHttpClient())
Expand All @@ -38,7 +54,7 @@ public async Task<JiraIssue> GetIssue(string workItemId)
}

var msg =
$"Failed to retrieve Jira issue '{workItemId}' from {baseUrl}. Status Code: {response.StatusCode}{(!string.IsNullOrEmpty(response.ReasonPhrase) ? $" (Reason: {response.ReasonPhrase})" : "")}";
$"Failed to retrieve Jira issue '{workItemId}' from {baseUrl}. Response Code: {response.StatusCode}{(!string.IsNullOrEmpty(response.ReasonPhrase) ? $" (Reason: {response.ReasonPhrase})" : "")}";
if(response.StatusCode == HttpStatusCode.NotFound)
log.Trace(msg);
else
Expand All @@ -56,7 +72,7 @@ public async Task<JiraIssueComments> GetIssueComments(string workItemId)
return JsonConvert.DeserializeObject<JiraIssueComments>(await response.Content.ReadAsStringAsync());

var msg =
$"Failed to retrieve comments for Jira issue '{workItemId}' from {baseUrl}. Status Code: {response.StatusCode}{(!string.IsNullOrEmpty(response.ReasonPhrase) ? $" (Reason: {response.ReasonPhrase})" : "")}";
$"Failed to retrieve comments for Jira issue '{workItemId}' from {baseUrl}. Response Code: {response.StatusCode}{(!string.IsNullOrEmpty(response.ReasonPhrase) ? $" (Reason: {response.ReasonPhrase})" : "")}";
if (response.StatusCode == HttpStatusCode.NotFound)
log.Trace(msg);
else
Expand Down
22 changes: 22 additions & 0 deletions source/Server/JiraIssueTrackerApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Threading.Tasks;
using Octopus.Server.Extensibility.Extensions.Infrastructure.Web.Api;
using Octopus.Server.Extensibility.IssueTracker.Jira.Configuration;
using Octopus.Server.Extensibility.IssueTracker.Jira.Web;

namespace Octopus.Server.Extensibility.IssueTracker.Jira
{
public class JiraIssueTrackerApi : RegisterEndpoint
{
public const string ApiConnectAppCredentialsTest = "/api/jiraissuetracker/connectivitycheck/connectapp";
public const string ApiJiraCredentialsTest = "/api/jiraissuetracker/connectivitycheck/jira";

public JiraIssueTrackerApi(
Func<SecuredAsyncActionInvoker<JiraConnectAppConnectivityCheckAction, IJiraConfigurationStore>> jiraConnectAppConnectivityCheckInvokerFactory,
Func<SecuredAsyncActionInvoker<JiraCredentialsConnectivityCheckAction, IJiraConfigurationStore>> jiraCredentialsConnectivityCheckInvokerFactory)
{
Add("POST", ApiJiraCredentialsTest, jiraCredentialsConnectivityCheckInvokerFactory().ExecuteAsync);
Add("POST", ApiConnectAppCredentialsTest, jiraConnectAppConnectivityCheckInvokerFactory().ExecuteAsync);
}
}
}
10 changes: 10 additions & 0 deletions source/Server/JiraIssueTrackerExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
using Octopus.Server.Extensibility.Extensions.Mappings;
using Octopus.Server.Extensibility.Extensions.Model.Environments;
using Octopus.Server.Extensibility.Extensions.WorkItems;
using Octopus.Server.Extensibility.HostServices.Web;
using Octopus.Server.Extensibility.IssueTracker.Jira.Configuration;
using Octopus.Server.Extensibility.IssueTracker.Jira.Deployments;
using Octopus.Server.Extensibility.IssueTracker.Jira.Environments;
using Octopus.Server.Extensibility.IssueTracker.Jira.Integration;
using Octopus.Server.Extensibility.IssueTracker.Jira.Web;
using Octopus.Server.Extensibility.IssueTracker.Jira.WorkItems;

namespace Octopus.Server.Extensibility.IssueTracker.Jira
Expand All @@ -37,6 +39,8 @@ public void Load(ContainerBuilder builder)
.As<IContributeMappings>()
.InstancePerLifetimeScope();

builder.RegisterType<JiraConnectAppClient>().AsSelf().InstancePerDependency();

builder.RegisterType<JiraIssueTracker>()
.As<IIssueTracker>()
.InstancePerLifetimeScope();
Expand All @@ -49,6 +53,9 @@ public void Load(ContainerBuilder builder)
.As<IContributeToConfigureCommand>()
.InstancePerDependency();

builder.RegisterType<JiraConnectAppConnectivityCheckAction>().AsSelf().InstancePerDependency();
builder.RegisterType<JiraCredentialsConnectivityCheckAction>().AsSelf().InstancePerDependency();

builder.RegisterType<CommentParser>().AsSelf().InstancePerDependency();
builder.RegisterType<WorkItemLinkMapper>().As<IWorkItemLinkMapper>().InstancePerDependency();

Expand All @@ -65,6 +72,9 @@ public void Load(ContainerBuilder builder)
return new JiraRestClient(baseUrl, username, password, log);
}).As<IJiraRestClient>()
.InstancePerDependency();

builder.RegisterType<JiraIssueTrackerHomeLinksContributor>().As<IHomeLinksContributor>()
.InstancePerDependency();
}
}
}
29 changes: 29 additions & 0 deletions source/Server/JiraIssueTrackerHomeLinksContributor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using Octopus.Server.Extensibility.HostServices.Web;
using Octopus.Server.Extensibility.IssueTracker.Jira.Configuration;

namespace Octopus.Server.Extensibility.IssueTracker.Jira
{
public class JiraIssueTrackerHomeLinksContributor : IHomeLinksContributor
{
private readonly IJiraConfigurationStore configurationStore;
public const string ApiConnectAppCredentialsTestLinkName = "JiraConnectAppCredentialsTest";
public const string ApiJiraCredentialsTestLinkName = "JiraCredentialsTest";

public JiraIssueTrackerHomeLinksContributor(IJiraConfigurationStore configurationStore)
{
this.configurationStore = configurationStore;
}

public IDictionary<string, string> GetLinksToContribute()
{
var linksToContribute = new Dictionary<string, string>
{
{ApiConnectAppCredentialsTestLinkName, $"~{JiraIssueTrackerApi.ApiConnectAppCredentialsTest}"},
{ApiJiraCredentialsTestLinkName, $"~{JiraIssueTrackerApi.ApiJiraCredentialsTest}"}
};

return linksToContribute;
}
}
}
2 changes: 1 addition & 1 deletion source/Server/Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageProjectUrl>https://github.com/OctopusDeploy/JiraIssueTracker</PackageProjectUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octopus.Data" Version="4.2.0" />
<PackageReference Include="Octopus.Data" Version="4.2.1" />
<PackageReference Include="Octopus.Diagnostics" Version="1.3.0" />
<PackageReference Include="Octopus.Server.Extensibility" Version="7.0.0" />
<PackageReference Include="Octopus.Time" Version="1.1.6" />
Expand Down
Loading