From 35f1784781cc2783f8d7a68b1c746e53b5379a74 Mon Sep 17 00:00:00 2001 From: Aaron Junker-Wildi Date: Mon, 29 Jul 2024 22:43:50 +0200 Subject: [PATCH] Adding support for creating Codespaces and getting available machine types (#2929) Adding support for creating Codespaces --- .../IObservableCodespacesClient.cs | 2 ++ .../ObservableCodespacesClient.cs | 15 ++++++++ .../Clients/CodespacesClientTests.cs | 15 ++++++++ .../Clients/CodespacesClientTests.cs | 19 +++++++++++ Octokit/Clients/Codespace.cs | 7 ++-- Octokit/Clients/CodespacesClient.cs | 24 +++++++++++++ Octokit/Clients/ICodespacesClient.cs | 5 ++- Octokit/Clients/MachinesCollection.cs | 26 ++++++++++++++ Octokit/Helpers/ApiUrls.cs | 21 ++++++++++++ Octokit/Models/Common/CodespaceLocation.cs | 16 +++++++++ Octokit/Models/Request/NewCodespace.cs | 34 +++++++++++++++++++ 11 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 Octokit/Clients/MachinesCollection.cs create mode 100644 Octokit/Models/Common/CodespaceLocation.cs create mode 100644 Octokit/Models/Request/NewCodespace.cs diff --git a/Octokit.Reactive/IObservableCodespacesClient.cs b/Octokit.Reactive/IObservableCodespacesClient.cs index cb3e67ba..07e40ef8 100644 --- a/Octokit.Reactive/IObservableCodespacesClient.cs +++ b/Octokit.Reactive/IObservableCodespacesClient.cs @@ -16,5 +16,7 @@ namespace Octokit.Reactive IObservable Get(string codespaceName); IObservable Start(string codespaceName); IObservable Stop(string codespaceName); + IObservable GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null); + IObservable Create(string owner, string repo, NewCodespace newCodespace); } } \ No newline at end of file diff --git a/Octokit.Reactive/ObservableCodespacesClient.cs b/Octokit.Reactive/ObservableCodespacesClient.cs index b8c65753..477e65dc 100644 --- a/Octokit.Reactive/ObservableCodespacesClient.cs +++ b/Octokit.Reactive/ObservableCodespacesClient.cs @@ -25,6 +25,13 @@ namespace Octokit.Reactive return _client.GetAll().ToObservable(); } + public IObservable GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null) + { + Ensure.ArgumentNotNull(repoOwner, nameof(repoOwner)); + Ensure.ArgumentNotNull(repoName, nameof(repoName)); + return _client.GetAvailableMachinesForRepo(repoOwner, repoName, reference).ToObservable(); + } + public IObservable GetForRepository(string owner, string repo) { Ensure.ArgumentNotNull(owner, nameof(owner)); @@ -43,5 +50,13 @@ namespace Octokit.Reactive Ensure.ArgumentNotNull(codespaceName, nameof(codespaceName)); return _client.Stop(codespaceName).ToObservable(); } + + public IObservable Create(string owner, string repo, NewCodespace newCodespace) + { + Ensure.ArgumentNotNull(owner, nameof(owner)); + Ensure.ArgumentNotNull(repo, nameof(repo)); + Ensure.ArgumentNotNull(newCodespace, nameof(newCodespace)); + return _client.Create(owner, repo, newCodespace).ToObservable(); + } } } \ No newline at end of file diff --git a/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs b/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs index f1b63423..01047a06 100644 --- a/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/CodespacesClientTests.cs @@ -56,4 +56,19 @@ public class CodespacesClientTests var retrieved = await _fixture.Stop(codespaceName); Assert.NotNull(retrieved); } + + [IntegrationTest] + public async Task CanGetAvailableMachinesForRepo() + { + var retrieved = await _fixture.GetAvailableMachinesForRepo(Helper.UserName, Helper.RepositoryWithCodespaces); + Assert.NotNull(retrieved); + } + + [IntegrationTest] + public async Task CanCreateCodespace() + { + MachinesCollection machinesCollection = (await _fixture.GetAvailableMachinesForRepo(Helper.UserName, Helper.RepositoryWithCodespaces)); + var retrieved = await _fixture.Create(Helper.UserName, Helper.RepositoryWithCodespaces, new NewCodespace(machinesCollection.Machines.First())); + Assert.NotNull(retrieved); + } } diff --git a/Octokit.Tests/Clients/CodespacesClientTests.cs b/Octokit.Tests/Clients/CodespacesClientTests.cs index 41fad4a5..12067b85 100644 --- a/Octokit.Tests/Clients/CodespacesClientTests.cs +++ b/Octokit.Tests/Clients/CodespacesClientTests.cs @@ -68,5 +68,24 @@ public class CodespacesClientTests client.Stop("codespaceName"); connection.Received().Post(Arg.Is(u => u.ToString() == "user/codespaces/codespaceName/stop")); } + + [Fact] + public void RequestsCorrectGetAvailableMachinesForRepoUrl() + { + var connection = Substitute.For(); + var client = new CodespacesClient(connection); + client.GetAvailableMachinesForRepo("owner", "repo"); + connection.Received().Get(Arg.Is(u => u.ToString() == "repos/owner/repo/codespaces/machines")); + } + + [Fact] + public void RequestsCorrectCreateNewCodespaceUrl() + { + var connection = Substitute.For(); + var client = new CodespacesClient(connection); + NewCodespace newCodespace = new NewCodespace(new Machine()); + client.Create("owner", "repo", newCodespace); + connection.Received().Post(Arg.Is(u => u.ToString() == "repos/owner/repo/codespaces"), newCodespace); + } } } diff --git a/Octokit/Clients/Codespace.cs b/Octokit/Clients/Codespace.cs index 91f1beb4..8a44b9a2 100644 --- a/Octokit/Clients/Codespace.cs +++ b/Octokit/Clients/Codespace.cs @@ -1,4 +1,5 @@ -using System; +using Octokit.Clients; +using System; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; @@ -23,8 +24,9 @@ namespace Octokit public string WebUrl { get; private set; } public string StartUrl { get; private set; } public string StopUrl { get; private set; } + public StringEnum Location { get; private set; } - public Codespace(long id, string name, User owner, User billableOwner, Repository repository, Machine machine, DateTime createdAt, DateTime updatedAt, DateTime lastUsedAt, StringEnum state, string url, string machinesUrl, string webUrl, string startUrl, string stopUrl) + public Codespace(long id, string name, User owner, User billableOwner, Repository repository, Machine machine, DateTime createdAt, DateTime updatedAt, DateTime lastUsedAt, StringEnum state, string url, string machinesUrl, string webUrl, string startUrl, string stopUrl, StringEnum location) { Id = id; Name = name; @@ -41,6 +43,7 @@ namespace Octokit WebUrl = webUrl; StartUrl = startUrl; StopUrl = stopUrl; + Location = location; } public Codespace() { } diff --git a/Octokit/Clients/CodespacesClient.cs b/Octokit/Clients/CodespacesClient.cs index afe47ce8..23c0b9e4 100644 --- a/Octokit/Clients/CodespacesClient.cs +++ b/Octokit/Clients/CodespacesClient.cs @@ -76,5 +76,29 @@ namespace Octokit { return ApiConnection.Post(ApiUrls.CodespaceStop(codespaceName)); } + + /// + /// Returns available machines for the specified repository. + /// + [ManualRoute("GET", "/repos/{repoOwner}/{repoName}/machines")] + public Task GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null) + { + Ensure.ArgumentNotNullOrEmptyString(repoOwner, nameof(repoOwner)); + Ensure.ArgumentNotNullOrEmptyString(repoName, nameof(repoName)); + + return ApiConnection.Get(ApiUrls.GetAvailableMachinesForRepo(repoOwner, repoName, reference)); + } + + /// + /// Creates a new codespace for the authenticated user. + /// + [ManualRoute("POST", "/repos/{owner}/{repo}/codespaces")] + public Task Create(string owner, string repo, NewCodespace newCodespace) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repo, nameof(repo)); + + return ApiConnection.Post(ApiUrls.CreateCodespace(owner, repo), newCodespace); + } } } diff --git a/Octokit/Clients/ICodespacesClient.cs b/Octokit/Clients/ICodespacesClient.cs index a3c630d5..e26ed864 100644 --- a/Octokit/Clients/ICodespacesClient.cs +++ b/Octokit/Clients/ICodespacesClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Octokit @@ -10,5 +11,7 @@ namespace Octokit Task Get(string codespaceName); Task Start(string codespaceName); Task Stop(string codespaceName); + Task GetAvailableMachinesForRepo(string repoOwner, string repoName, string reference = null); + Task Create(string owner, string repo, NewCodespace newCodespace); } } \ No newline at end of file diff --git a/Octokit/Clients/MachinesCollection.cs b/Octokit/Clients/MachinesCollection.cs new file mode 100644 index 00000000..af47b65f --- /dev/null +++ b/Octokit/Clients/MachinesCollection.cs @@ -0,0 +1,26 @@ +using Octokit.Internal; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class MachinesCollection + { + public MachinesCollection(IReadOnlyList machines, int count) + { + Machines = machines; + Count = count; + } + + public MachinesCollection() { } + + [Parameter(Key = "total_count")] + public int Count { get; private set; } + [Parameter(Key = "machines")] + public IReadOnlyList Machines { get; private set; } = new List(); + + internal string DebuggerDisplay => string.Format(CultureInfo.CurrentCulture, "MachinesCollection: Count: {0}", Count); + } +} \ No newline at end of file diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index dc6d12e9..a7437f17 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -5604,6 +5604,22 @@ namespace Octokit return "orgs/{0}/actions/runner-groups/{1}/repositories".FormatUri(org, runnerGroupId); } + /// + /// Returns the that handles the machine availability for a repository. + /// + /// The owner of the repository. + /// The name of the repository. + /// The reference to check the machine availability for. + public static Uri GetAvailableMachinesForRepo(string owner, string repo, string reference) + { + if (reference is null) + { + return "repos/{0}/{1}/codespaces/machines".FormatUri(owner, repo); + } + + return "repos/{0}/{1}/actions/runners/availability?ref={3}".FormatUri(owner, repo, reference); + } + /// /// Returns the that handles adding or removing of copilot licenses for an organisation /// @@ -5659,6 +5675,11 @@ namespace Octokit return "user/codespaces/{0}/stop".FormatUri(codespaceName); } + public static Uri CreateCodespace(string owner, string repo) + { + return "repos/{0}/{1}/codespaces".FormatUri(owner, repo); + } + /// /// Returns the that lists the artifacts for a repository. /// diff --git a/Octokit/Models/Common/CodespaceLocation.cs b/Octokit/Models/Common/CodespaceLocation.cs new file mode 100644 index 00000000..d00d4448 --- /dev/null +++ b/Octokit/Models/Common/CodespaceLocation.cs @@ -0,0 +1,16 @@ +using Octokit.Internal; + +namespace Octokit +{ + public enum CodespaceLocation + { + [Parameter(Value = "EuropeWest")] + EuropeWest, + [Parameter(Value = "SoutheastAsia")] + SoutheastAsia, + [Parameter(Value = "UsEast")] + UsEast, + [Parameter(Value = "UsWest")] + UsWest + } +} diff --git a/Octokit/Models/Request/NewCodespace.cs b/Octokit/Models/Request/NewCodespace.cs new file mode 100644 index 00000000..3be0bf99 --- /dev/null +++ b/Octokit/Models/Request/NewCodespace.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class NewCodespace + { + public string MachineType { get; set; } + public string Reference { get; set; } + public CodespaceLocation? Location { get; set; } + public string DisplayName { get; set; } + + public NewCodespace(Machine machineType, string reference = "main", CodespaceLocation? location = null, string displayName = null) + { + MachineType = machineType.Name; + Reference = reference; + Location = location; + DisplayName = displayName; + } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "NewCodespace Repo: {0}", DisplayName); + } + } + + } +}