diff --git a/NGitLab.Mock/Clients/GroupVariableClient.cs b/NGitLab.Mock/Clients/GroupVariableClient.cs index 1a12457c..e135f2f3 100644 --- a/NGitLab.Mock/Clients/GroupVariableClient.cs +++ b/NGitLab.Mock/Clients/GroupVariableClient.cs @@ -14,7 +14,9 @@ public GroupVariableClient(ClientContext context, GroupId groupId) _groupId = Server.AllGroups.FindGroup(groupId.ValueAsString()).Id; } - public Variable this[string key] => throw new NotImplementedException(); + public Variable this[string key] => this[key, null]; + + public Variable this[string key, string environmentScope] => throw new NotImplementedException(); public IEnumerable<Variable> All => throw new NotImplementedException(); @@ -23,12 +25,16 @@ public Variable Create(VariableCreate model) throw new NotImplementedException(); } - public void Delete(string key) + public void Delete(string key) => Delete(key, null); + + public void Delete(string key, string environmentScope) { throw new NotImplementedException(); } - public Variable Update(string key, VariableUpdate model) + public Variable Update(string key, VariableUpdate model) => Update(key, null, model); + + public Variable Update(string key, string environmentScope, VariableUpdate model) { throw new NotImplementedException(); } diff --git a/NGitLab.Mock/Clients/ProjectVariableClient.cs b/NGitLab.Mock/Clients/ProjectVariableClient.cs index 137dfb03..ce88da54 100644 --- a/NGitLab.Mock/Clients/ProjectVariableClient.cs +++ b/NGitLab.Mock/Clients/ProjectVariableClient.cs @@ -14,7 +14,9 @@ public ProjectVariableClient(ClientContext context, ProjectId projectId) _projectId = Server.AllProjects.FindProject(projectId.ValueAsString()).Id; } - public Variable this[string key] => throw new NotImplementedException(); + public Variable this[string key] => this[key, null]; + + public Variable this[string key, string environmentScope] => throw new NotImplementedException(); public IEnumerable<Variable> All => throw new NotImplementedException(); @@ -23,12 +25,16 @@ public Variable Create(VariableCreate model) throw new NotImplementedException(); } - public void Delete(string key) + public void Delete(string key) => Delete(key, null); + + public void Delete(string key, string environmentScope) { throw new NotImplementedException(); } - public Variable Update(string key, VariableUpdate model) + public Variable Update(string key, VariableUpdate model) => Update(key, null, model); + + public Variable Update(string key, string environmentScope, VariableUpdate model) { throw new NotImplementedException(); } diff --git a/NGitLab.Tests/GroupVariableClientTests.cs b/NGitLab.Tests/GroupVariableClientTests.cs index 067e3e2f..43e6f8ec 100644 --- a/NGitLab.Tests/GroupVariableClientTests.cs +++ b/NGitLab.Tests/GroupVariableClientTests.cs @@ -1,7 +1,9 @@ using System.Linq; +using System.Net; using System.Threading.Tasks; using NGitLab.Models; using NGitLab.Tests.Docker; +using NuGet.Versioning; using NUnit.Framework; namespace NGitLab.Tests; @@ -52,4 +54,67 @@ public async Task Test_group_variables() variables = groupVariableClient.All.ToList(); Assert.That(variables, Has.Count.EqualTo(3)); } + + [Test] + [NGitLabRetry] + public async Task Test_group_variables_with_complete_members() + { + using var context = await GitLabTestContext.CreateAsync(); + var group = context.CreateGroup(); + var groupVariableClient = context.Client.GetGroupVariableClient(group.Id); + + // Create + var variable = groupVariableClient.Create(new VariableCreate + { + Key = "My_Key", + Value = "My value", + Description = "Some important variable", + Protected = true, + Type = VariableType.Variable, + Masked = false, + Raw = false, + }); + + Assert.That(variable.Key, Is.EqualTo("My_Key")); + Assert.That(variable.Value, Is.EqualTo("My value")); + + if (context.IsGitLabVersionInRange(VersionRange.Parse("[16.2,)"), out _)) + { + Assert.That(variable.Description, Is.EqualTo("Some important variable")); + } + + Assert.That(variable.Protected, Is.EqualTo(true)); + Assert.That(variable.Type, Is.EqualTo(VariableType.Variable)); + Assert.That(variable.Masked, Is.EqualTo(false)); + Assert.That(variable.Raw, Is.EqualTo(false)); + + // Update + var newScope = "integration/*"; + variable = groupVariableClient.Update(variable.Key, variable.EnvironmentScope, new VariableUpdate + { + Value = "My value edited", + Protected = false, + EnvironmentScope = newScope + }); + + Assert.That(variable.Key, Is.EqualTo("My_Key")); + Assert.That(variable.Value, Is.EqualTo("My value edited")); + Assert.That(variable.Protected, Is.EqualTo(false)); + + // Delete + var ex = Assert.Throws<GitLabException>(() => groupVariableClient.Delete(variable.Key, "wrongScope")); + Assert.That(ex!.StatusCode == HttpStatusCode.NotFound); + + groupVariableClient.Delete(variable.Key); + + var variables = groupVariableClient.All.ToList(); + Assert.That(variables, Is.Empty); + + // All + groupVariableClient.Create(new VariableCreate { Key = "Variable1", Value = "test", EnvironmentScope = "test/*" }); + groupVariableClient.Create(new VariableCreate { Key = "Variable2", Value = "test", EnvironmentScope = "integration" }); + groupVariableClient.Create(new VariableCreate { Key = "Variable3", Value = "test", EnvironmentScope = "*" }); + variables = groupVariableClient.All.ToList(); + Assert.That(variables, Has.Count.EqualTo(3)); + } } diff --git a/NGitLab.Tests/ProjectVariableClientTests.cs b/NGitLab.Tests/ProjectVariableClientTests.cs index 87b5d673..096ea5cb 100644 --- a/NGitLab.Tests/ProjectVariableClientTests.cs +++ b/NGitLab.Tests/ProjectVariableClientTests.cs @@ -1,7 +1,10 @@ using System.Linq; +using System.Net; using System.Threading.Tasks; +using NGitLab.Extensions; using NGitLab.Models; using NGitLab.Tests.Docker; +using NuGet.Versioning; using NUnit.Framework; namespace NGitLab.Tests; @@ -52,4 +55,94 @@ public async Task Test_project_variables() variables = projectVariableClient.All.ToList(); Assert.That(variables, Has.Count.EqualTo(3)); } + + [Test] + [NGitLabRetry] + public async Task Test_project_variables_with_scope() + { + using var context = await GitLabTestContext.CreateAsync(); + var project = context.CreateProject(); + var projectVariableClient = context.Client.GetProjectVariableClient(project.Id); + + // Create + var firstScope = "test/*"; + var changingScopeVariable = projectVariableClient.Create(new VariableCreate + { + Key = "My_Key", + Value = "My value", + Description = "Some important variable", + Protected = true, + Type = VariableType.Variable, + Masked = false, + Raw = false, + EnvironmentScope = firstScope, + }); + + var integrationScope = "integration/*"; + var integrationVariable = projectVariableClient.Create(new VariableCreate + { + Key = "My_Key", + Value = "My value", + Description = "Some important variable", + Protected = true, + Type = VariableType.Variable, + Masked = false, + Raw = false, + EnvironmentScope = integrationScope, + }); + + Assert.That(changingScopeVariable.Key, Is.EqualTo("My_Key")); + Assert.That(changingScopeVariable.Value, Is.EqualTo("My value")); + + if (context.IsGitLabVersionInRange(VersionRange.Parse("[16.2,)"), out _)) + { + Assert.That(changingScopeVariable.Description, Is.EqualTo("Some important variable")); + } + + Assert.That(changingScopeVariable.Protected, Is.EqualTo(true)); + Assert.That(changingScopeVariable.Type, Is.EqualTo(VariableType.Variable)); + Assert.That(changingScopeVariable.Masked, Is.EqualTo(false)); + Assert.That(changingScopeVariable.Raw, Is.EqualTo(false)); + Assert.That(changingScopeVariable.EnvironmentScope, Is.EqualTo(firstScope)); + + // Check single access with scoped variables + Assert.That(projectVariableClient[changingScopeVariable.Key, changingScopeVariable.EnvironmentScope], Is.Not.Null); + + var exMultipleVariables = Assert.Throws<GitLabException>(() => + { + var dummy = projectVariableClient[changingScopeVariable.Key]; + }); + Assert.That(exMultipleVariables?.ErrorMessage, Is.EqualTo("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")); + + // Update + var newScope = "production/*"; + changingScopeVariable = projectVariableClient.Update(changingScopeVariable.Key, changingScopeVariable.EnvironmentScope, new VariableUpdate + { + Value = "My value edited", + Protected = false, + EnvironmentScope = newScope, + }); + + Assert.That(changingScopeVariable.Key, Is.EqualTo("My_Key")); + Assert.That(changingScopeVariable.Value, Is.EqualTo("My value edited")); + Assert.That(changingScopeVariable.Protected, Is.EqualTo(false)); + Assert.That(changingScopeVariable.EnvironmentScope, Is.EqualTo(newScope)); + + // Delete + var ex = Assert.Throws<GitLabException>(() => projectVariableClient.Delete(changingScopeVariable.Key, "wrongScope")); + Assert.That(ex!.StatusCode == HttpStatusCode.NotFound); + + projectVariableClient.Delete(changingScopeVariable.Key, newScope); + projectVariableClient.Delete(integrationVariable.Key, integrationScope); + + var variables = projectVariableClient.All.ToList(); + Assert.That(variables, Is.Empty); + + // All + projectVariableClient.Create(new VariableCreate { Key = "Variable1", Value = "test", EnvironmentScope = "test/*" }); + projectVariableClient.Create(new VariableCreate { Key = "Variable2", Value = "test", EnvironmentScope = "integration" }); + projectVariableClient.Create(new VariableCreate { Key = "Variable3", Value = "test", EnvironmentScope = "*" }); + variables = projectVariableClient.All.ToList(); + Assert.That(variables, Has.Count.EqualTo(3)); + } } diff --git a/NGitLab/IProjectBadgeClient.cs b/NGitLab/IProjectBadgeClient.cs index 4402fe30..17b26324 100644 --- a/NGitLab/IProjectBadgeClient.cs +++ b/NGitLab/IProjectBadgeClient.cs @@ -28,11 +28,17 @@ public interface IProjectVariableClient Variable this[string key] { get; } + Variable this[string key, string environmentScope] { get; } + Variable Create(VariableCreate model); Variable Update(string key, VariableUpdate model); + Variable Update(string key, string environmentScope, VariableUpdate model); + void Delete(string key); + + void Delete(string key, string environmentScope); } public interface IGroupVariableClient @@ -41,9 +47,15 @@ public interface IGroupVariableClient Variable this[string key] { get; } + Variable this[string key, string environmentScope] { get; } + Variable Create(VariableCreate model); Variable Update(string key, VariableUpdate model); + Variable Update(string key, string environmentScope, VariableUpdate model); + void Delete(string key); + + void Delete(string key, string environmentScope); } diff --git a/NGitLab/Impl/VariableClient.cs b/NGitLab/Impl/VariableClient.cs index 12fce92a..0751c443 100644 --- a/NGitLab/Impl/VariableClient.cs +++ b/NGitLab/Impl/VariableClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using NGitLab.Models; namespace NGitLab.Impl; @@ -8,6 +9,8 @@ internal abstract class VariableClient private readonly string _urlPrefix; private readonly API _api; + private string EnvironmentScopeFilter(string environmentScope = null) => !string.IsNullOrWhiteSpace(environmentScope) ? $"?filter[environment_scope]={Uri.EscapeDataString(environmentScope)}" : string.Empty; + protected VariableClient(API api, string urlPrefix) { _urlPrefix = urlPrefix; @@ -16,11 +19,17 @@ protected VariableClient(API api, string urlPrefix) public IEnumerable<Variable> All => _api.Get().GetAll<Variable>(_urlPrefix + "/variables"); - public Variable this[string key] => _api.Get().To<Variable>(_urlPrefix + "/variables/" + key); + public Variable this[string key] => this[key, null]; + + public Variable this[string key, string environmentScope] => _api.Get().To<Variable>($"{_urlPrefix}/variables/{key}{EnvironmentScopeFilter(environmentScope)}"); public Variable Create(VariableCreate model) => _api.Post().With(model).To<Variable>(_urlPrefix + "/variables"); - public Variable Update(string key, VariableUpdate model) => _api.Put().With(model).To<Variable>(_urlPrefix + "/variables/" + key); + public Variable Update(string key, VariableUpdate model) => Update(key, null, model); + + public Variable Update(string key, string environmentScope, VariableUpdate model) => _api.Put().With(model).To<Variable>($"{_urlPrefix}/variables/{key}{EnvironmentScopeFilter(environmentScope)}"); + + public void Delete(string key) => Delete(key, null); - public void Delete(string key) => _api.Delete().Execute(_urlPrefix + "/variables/" + key); + public void Delete(string key, string environmentScope) => _api.Delete().Execute($"{_urlPrefix}/variables/{key}{EnvironmentScopeFilter(environmentScope)}"); } diff --git a/NGitLab/Models/Variable.cs b/NGitLab/Models/Variable.cs index b459e39c..a254dbc5 100644 --- a/NGitLab/Models/Variable.cs +++ b/NGitLab/Models/Variable.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.ComponentModel; +using System.Text.Json.Serialization; namespace NGitLab.Models; @@ -10,6 +11,14 @@ public class Variable [JsonPropertyName("value")] public string Value { get; set; } + /// <summary> + /// The description of a variable + /// </summary> + /// <returns>The description of a variable</returns> + /// <remarks>Introduced in GitLab 16.2</remarks> + [JsonPropertyName("description")] + public string Description { get; set; } + [JsonPropertyName("protected")] public bool Protected { get; set; } @@ -19,6 +28,23 @@ public class Variable [JsonPropertyName("masked")] public bool Masked { get; set; } + [JsonPropertyName("raw")] + public bool Raw { get; set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public string Scope + { + get => EnvironmentScope; + set => EnvironmentScope = value; + } + + /// <summary> + /// The environment scope of a variable + /// </summary> + /// <remarks> + /// Create and Update of project variable: All tiers (Free, Premium, Ultimate).<br/> + /// Create and Update of group variable: Premium and Ultimate only. + /// </remarks> [JsonPropertyName("environment_scope")] - public string Scope { get; set; } + public string EnvironmentScope { get; set; } } diff --git a/NGitLab/Models/VariableCreate.cs b/NGitLab/Models/VariableCreate.cs index dbe53e1b..c264d4b1 100644 --- a/NGitLab/Models/VariableCreate.cs +++ b/NGitLab/Models/VariableCreate.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.ComponentModel; +using System.Text.Json.Serialization; namespace NGitLab.Models; @@ -10,6 +11,33 @@ public class VariableCreate [JsonPropertyName("value")] public string Value; + /// <summary> + /// The description of a variable + /// </summary> + /// <returns>The description of a variable</returns> + /// <remarks>Introduced in GitLab 16.2</remarks> + [JsonPropertyName("description")] + public string Description; + [JsonPropertyName("protected")] public bool Protected; + + [JsonPropertyName("variable_type")] + public VariableType Type; + + [JsonPropertyName("masked")] + public bool Masked; + + [JsonPropertyName("raw")] + public bool Raw; + + /// <summary> + /// The environment scope of a variable + /// </summary> + /// <remarks> + /// Create and Update of project variable: All tiers (Free, Premium, Ultimate).<br/> + /// Create and Update of group variable: Premium and Ultimate only. + /// </remarks> + [JsonPropertyName("environment_scope")] + public string EnvironmentScope; } diff --git a/NGitLab/Models/VariableUpdate.cs b/NGitLab/Models/VariableUpdate.cs index da5cd57a..a0a473e6 100644 --- a/NGitLab/Models/VariableUpdate.cs +++ b/NGitLab/Models/VariableUpdate.cs @@ -7,6 +7,33 @@ public class VariableUpdate [JsonPropertyName("value")] public string Value; + /// <summary> + /// The description of a variable + /// </summary> + /// <returns>The description of a variable</returns> + /// <remarks>Introduced in GitLab 16.2</remarks> + [JsonPropertyName("description")] + public string Description; + [JsonPropertyName("protected")] public bool Protected; + + [JsonPropertyName("variable_type")] + public VariableType Type; + + [JsonPropertyName("masked")] + public bool Masked; + + [JsonPropertyName("raw")] + public bool Raw; + + /// <summary> + /// The environment scope of a variable + /// </summary> + /// <remarks> + /// Create and Update of project variable: All tiers (Free, Premium, Ultimate).<br/> + /// Create and Update of group variable: Premium and Ultimate only. + /// </remarks> + [JsonPropertyName("environment_scope")] + public string EnvironmentScope; } diff --git a/NGitLab/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI.Unshipped.txt index a647d2f6..6a16a65a 100644 --- a/NGitLab/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI.Unshipped.txt @@ -306,8 +306,11 @@ NGitLab.IGroupVariableClient NGitLab.IGroupVariableClient.All.get -> System.Collections.Generic.IEnumerable<NGitLab.Models.Variable> NGitLab.IGroupVariableClient.Create(NGitLab.Models.VariableCreate model) -> NGitLab.Models.Variable NGitLab.IGroupVariableClient.Delete(string key) -> void +NGitLab.IGroupVariableClient.Delete(string key, string environmentScope) -> void +NGitLab.IGroupVariableClient.this[string key, string environmentScope].get -> NGitLab.Models.Variable NGitLab.IGroupVariableClient.this[string key].get -> NGitLab.Models.Variable NGitLab.IGroupVariableClient.Update(string key, NGitLab.Models.VariableUpdate model) -> NGitLab.Models.Variable +NGitLab.IGroupVariableClient.Update(string key, string environmentScope, NGitLab.Models.VariableUpdate model) -> NGitLab.Models.Variable NGitLab.IHttpRequestor NGitLab.IHttpRequestor.Execute(string tailAPIUrl) -> void NGitLab.IHttpRequestor.ExecuteAsync(string tailAPIUrl, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task @@ -1075,8 +1078,11 @@ NGitLab.IProjectVariableClient NGitLab.IProjectVariableClient.All.get -> System.Collections.Generic.IEnumerable<NGitLab.Models.Variable> NGitLab.IProjectVariableClient.Create(NGitLab.Models.VariableCreate model) -> NGitLab.Models.Variable NGitLab.IProjectVariableClient.Delete(string key) -> void +NGitLab.IProjectVariableClient.Delete(string key, string environmentScope) -> void +NGitLab.IProjectVariableClient.this[string key, string environmentScope].get -> NGitLab.Models.Variable NGitLab.IProjectVariableClient.this[string key].get -> NGitLab.Models.Variable NGitLab.IProjectVariableClient.Update(string key, NGitLab.Models.VariableUpdate model) -> NGitLab.Models.Variable +NGitLab.IProjectVariableClient.Update(string key, string environmentScope, NGitLab.Models.VariableUpdate model) -> NGitLab.Models.Variable NGitLab.IProtectedBranchClient NGitLab.IProtectedBranchClient.GetProtectedBranch(string branchName) -> NGitLab.Models.ProtectedBranch NGitLab.IProtectedBranchClient.GetProtectedBranches(string search = null) -> NGitLab.Models.ProtectedBranch[] @@ -3911,12 +3917,18 @@ NGitLab.Models.UserUpsert.Username -> string NGitLab.Models.UserUpsert.UserUpsert() -> void NGitLab.Models.UserUpsert.WebsiteURL -> string NGitLab.Models.Variable +NGitLab.Models.Variable.Description.get -> string +NGitLab.Models.Variable.Description.set -> void NGitLab.Models.Variable.Key.get -> string NGitLab.Models.Variable.Key.set -> void NGitLab.Models.Variable.Masked.get -> bool NGitLab.Models.Variable.Masked.set -> void NGitLab.Models.Variable.Protected.get -> bool NGitLab.Models.Variable.Protected.set -> void +NGitLab.Models.Variable.Raw.get -> bool +NGitLab.Models.Variable.Raw.set -> void +NGitLab.Models.Variable.EnvironmentScope.get -> string +NGitLab.Models.Variable.EnvironmentScope.set -> void NGitLab.Models.Variable.Scope.get -> string NGitLab.Models.Variable.Scope.set -> void NGitLab.Models.Variable.Type.get -> NGitLab.Models.VariableType @@ -3925,15 +3937,25 @@ NGitLab.Models.Variable.Value.get -> string NGitLab.Models.Variable.Value.set -> void NGitLab.Models.Variable.Variable() -> void NGitLab.Models.VariableCreate +NGitLab.Models.VariableCreate.Description -> string +NGitLab.Models.VariableCreate.EnvironmentScope -> string NGitLab.Models.VariableCreate.Key -> string +NGitLab.Models.VariableCreate.Masked -> bool NGitLab.Models.VariableCreate.Protected -> bool +NGitLab.Models.VariableCreate.Raw -> bool +NGitLab.Models.VariableCreate.Type -> NGitLab.Models.VariableType NGitLab.Models.VariableCreate.Value -> string NGitLab.Models.VariableCreate.VariableCreate() -> void NGitLab.Models.VariableType NGitLab.Models.VariableType.File = 1 -> NGitLab.Models.VariableType NGitLab.Models.VariableType.Variable = 0 -> NGitLab.Models.VariableType NGitLab.Models.VariableUpdate +NGitLab.Models.VariableUpdate.Description -> string +NGitLab.Models.VariableUpdate.EnvironmentScope -> string +NGitLab.Models.VariableUpdate.Masked -> bool NGitLab.Models.VariableUpdate.Protected -> bool +NGitLab.Models.VariableUpdate.Raw -> bool +NGitLab.Models.VariableUpdate.Type -> NGitLab.Models.VariableType NGitLab.Models.VariableUpdate.Value -> string NGitLab.Models.VariableUpdate.VariableUpdate() -> void NGitLab.Models.VisibilityLevel