Skip to content

Commit

Permalink
Add new User cmdlets (#224)
Browse files Browse the repository at this point in the history
* Update obsolete API

* Remove automated tweet

* Add new New-TfsUser cmdlet

* Update build tasks to use PowerShell 7

* Remove docgen from build

* Build before debug

* Add alias

* Update manifest version

* User three-part version for WinGet package

* Revert change

* Move JSON-related extensions to separate file

* Rename property to avoid conflict with DisplayName arguments

* Move template-based API call support to IRestApiService

* Add automatic conversion of JSON objects to PowerShell format

* Add new cmdlet

* Add Remove-TfsUser

* Fix typo

* Update release notes

+semver: minor

* Add proper support for project/team parameters
  • Loading branch information
igoravl authored Jul 9, 2024
1 parent 4e35fb0 commit 645b1fd
Show file tree
Hide file tree
Showing 20 changed files with 594 additions and 159 deletions.
29 changes: 16 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,16 +322,19 @@ jobs:
needs: [ Release, Nuget, PSGallery, Chocolatey, Site, WinGet ]
environment: announcements
steps:
- name: Tweet
id: tweet
uses: snow-actions/tweet@v1.3.0
env:
BUILD_NAME: ${{ needs.Release.outputs.BUILD_NAME }}
CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
CONSUMER_API_SECRET_KEY: ${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}
ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
RELEASE_TAG: ${{ needs.Release.outputs.RELEASE_TAG }}
with:
status: |
TfsCmdlets version ${{ env.BUILD_NAME }} has just been released. Check it out! https://github.com/igoravl/TfsCmdlets/releases/tag/v${{ env.RELEASE_TAG }}
- name: No-op
shell: bash
run: echo "no-op"
# - name: Tweet
# id: tweet
# uses: snow-actions/tweet@v1.3.0
# env:
# BUILD_NAME: ${{ needs.Release.outputs.BUILD_NAME }}
# CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
# CONSUMER_API_SECRET_KEY: ${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}
# ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
# ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
# RELEASE_TAG: ${{ needs.Release.outputs.RELEASE_TAG }}
# with:
# status: |
# TfsCmdlets version ${{ env.BUILD_NAME }} has just been released. Check it out! https://github.com/igoravl/TfsCmdlets/releases/tag/v${{ env.RELEASE_TAG }}
17 changes: 2 additions & 15 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@
"console": "externalTerminal",
"preLaunchTask": "Build"
},
{
"name": "(.NET) PowerShell 7 Preview",
"type": "coreclr",
"request": "launch",
"program": "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe",
"args": [
"-noexit",
"-command",
"Import-Module ${workspaceFolder}/out/module/TfsCmdlets.psd1; Enter-TfsShell"
],
"cwd": "${workspaceFolder}",
"console": "externalTerminal",
"preLaunchTask": "Build"
},
{
"name": "(.NET) PowerShell 7",
"type": "coreclr",
Expand All @@ -47,7 +33,8 @@
"Import-Module ${workspaceFolder}/out/module/TfsCmdlets.psd1; Enter-TfsShell"
],
"cwd": "${workspaceFolder}",
"console": "externalTerminal"
"console": "externalTerminal",
"preLaunchTask": "Build"
}
]
}
12 changes: 6 additions & 6 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"tasks": [
{
"label": "Build",
"command": "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
"command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"group": {
"kind": "build",
"isDefault": true
Expand Down Expand Up @@ -35,7 +35,7 @@
},
{
"label": "Rebuild",
"command": "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
"command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"presentation": {
"echo": true,
"reveal": "always",
Expand All @@ -62,7 +62,7 @@
},
{
"label": "Clean",
"command": "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
"command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"presentation": {
"echo": true,
"reveal": "always",
Expand All @@ -86,7 +86,7 @@
},
{
"label": "Package",
"command": "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
"command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"group": "none",
"presentation": {
"echo": true,
Expand Down Expand Up @@ -114,7 +114,7 @@
},
{
"label": "Test",
"command": "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
"command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"group": "test",
"presentation": {
"echo": true,
Expand All @@ -138,7 +138,7 @@
},
{
"label": "Docs",
"command": "c:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
"command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"presentation": {
"echo": true,
"reveal": "always",
Expand Down
19 changes: 16 additions & 3 deletions CSharp/TfsCmdlets.Common/Cmdlets/CmdletBase.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.Versioning;
using Newtonsoft.Json.Linq;
using TfsCmdlets.Util;

namespace TfsCmdlets.Cmdlets
Expand Down Expand Up @@ -41,7 +42,7 @@ public CmdletBase()
/// Returns the PowerShell command name of this cmdlet
/// </summary>
/// <value>The name of the this, as defined by the [Cmdlet] attribute. If the attribute is missing, returns the class name.</value>
public virtual string DisplayName
public virtual string CmdletDisplayName
{
get
{
Expand Down Expand Up @@ -110,7 +111,19 @@ protected virtual void DoProcessRecord()

if (!ReturnsValue || result == null) continue;

WriteObject(result, true);
switch (result)
{
case JToken j:
{
WriteObject(PSJsonConverter.Deserialize(j.ToString(), true), true);
break;
}
default:
{
WriteObject(result, true);
break;
}
}
}
}
catch (PipelineStoppedException)
Expand Down Expand Up @@ -151,7 +164,7 @@ private void InjectDependencies()

private void LogParameters()
{
Logger.LogParameters(DisplayName, Parameters);
Logger.LogParameters(CmdletDisplayName, Parameters);
}

private string GetVerb()
Expand Down
4 changes: 2 additions & 2 deletions CSharp/TfsCmdlets.Common/Controllers/ControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public abstract class ControllerBase : IController

public string Noun => GetType().Name.Substring(Verb.Length, GetType().Name.EndsWith("Controller") ? GetType().Name.Length - Verb.Length - 10 : GetType().Name.Length - Verb.Length);

public string DisplayName => $"{Verb}-Tfs{Noun}";
public string CmdletDisplayName => $"{Verb}-Tfs{Noun}";

public string CommandName => $"{Verb}{Noun}";

Expand Down Expand Up @@ -36,7 +36,7 @@ public IEnumerable InvokeCommand()
{
CacheParameters();

Logger.LogParameters(DisplayName, Parameters);
Logger.LogParameters(CmdletDisplayName, Parameters);

return Run();
}
Expand Down
52 changes: 52 additions & 0 deletions CSharp/TfsCmdlets.Common/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,56 @@ public enum QueryItemScope
/// </summary>
Both = 3
}


/// <summary>
/// License types available for Azure DevOps users.
/// </summary>
public enum AccountLicenseType
{
/// <summary>
/// "Basic" license
/// </summary>
Basic,

/// <summary>
/// "Basic + Test Plans" license
/// </summary>
BasicTestPlans,

/// <summary>
/// "Stakeholder" (free) license
/// </summary>
Stakeholder,

/// <summary>
/// "Visual Studio Subscriber" (Pro, Enterprise) license
/// </summary>
VisualStudio
}

/// <summary>
/// Represents the type of group entitlement.
/// </summary>
public enum GroupEntitlementType {
/// <summary>
/// Represents an administrator role.
/// </summary>
Administrator,

/// <summary>
/// Represents a contributor.
/// </summary>
Contributor,

/// <summary>
/// Represents a reader.
/// </summary>
Reader,

/// <summary>
/// Represents a stakeholder.
/// </summary>
Stakeholder
}
}
29 changes: 29 additions & 0 deletions CSharp/TfsCmdlets.Common/Extensions/JsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Newtonsoft.Json.Linq;
using System.Net.Http; //.HttpResponseMessage

namespace TfsCmdlets.Extensions {
public static class JsonExtensions {

public static JObject ToJsonObject(this string self)
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<JObject>(self);
}

public static T ToJsonObject<T>(this string self)
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(self);
}

public static JObject ToJsonObject(this HttpResponseMessage self)
{
var responseBody = self.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return ToJsonObject<JObject>(responseBody);
}

public static T ToJsonObject<T>(this HttpResponseMessage self)
{
var responseBody = self.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return ToJsonObject<T>(responseBody);
}
}
}
6 changes: 1 addition & 5 deletions CSharp/TfsCmdlets.Common/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Management.Automation;
using Newtonsoft.Json.Linq;

namespace TfsCmdlets.Extensions
{
Expand Down Expand Up @@ -32,10 +33,5 @@ public static int FindIndex(this string input, Predicate<char> predicate, int st

return -1;
}

public static T ToJsonObject<T>(this string self)
{
return (T) Newtonsoft.Json.JsonConvert.DeserializeObject(self);
}
}
}
62 changes: 62 additions & 0 deletions CSharp/TfsCmdlets.Common/Services/IRestApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,53 @@ namespace TfsCmdlets.Services
{
public interface IRestApiService
{
/// <summary>
/// Invokes an Azure DevOps REST API based on a template URL.
/// </summary>
/// <description>
/// This method is used to invoke an Azure DevOps REST API based on the template URL format used in the Azure DevOps REST API documentation.
/// </description>
/// <param name="connection">The connection information.</param>
/// <param name="apiTemplate">The API template.</param>
/// <param name="body">The request body (optional).</param>
/// <param name="method">The HTTP method (default is "GET").</param>
/// <param name="queryParameters">The query parameters (optional).</param>
/// <param name="requestContentType">The request content type (default is "application/json").</param>
/// <param name="responseContentType">The response content type (default is "application/json").</param>
/// <param name="additionalHeaders">Additional headers to include in the request (optional).</param>
/// <param name="apiVersion">The API version (default is "4.1").</param>
/// <param name="project">A delegate that returns the TeamProject, used only when there is a {project}/{projectId} parameter in the template URL (optional).</param>
/// <param name="team">A function that returns the Team, used only when there is a {team}/{teamId} parameter in the template URL (optional).</param>
/// <param name="customServiceHost">The custom service host (optional).</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task<HttpResponseMessage> InvokeTemplateAsync(
Models.Connection connection,
string apiTemplate,
string body = null,
string method = "GET",
IDictionary queryParameters = null,
string requestContentType = "application/json",
string responseContentType = "application/json",
Dictionary<string, string> additionalHeaders = null,
string apiVersion = "4.1",
Func<WebApiTeamProject> project = null,
Func<Models.Team> team = null,
string customServiceHost = null);

/// <summary>
/// Invokes an Azure DevOps REST API endpoint asynchronously.
/// </summary>
/// <param name="connection">The connection information.</param>
/// <param name="path">The path of the API endpoint.</param>
/// <param name="method">The HTTP method to use (default is "GET").</param>
/// <param name="body">The request body (optional).</param>
/// <param name="requestContentType">The content type of the request (default is "application/json").</param>
/// <param name="responseContentType">The expected content type of the response (default is "application/json").</param>
/// <param name="additionalHeaders">Additional headers to include in the request (optional).</param>
/// <param name="queryParameters">Query parameters to include in the request (optional).</param>
/// <param name="apiVersion">The version of the API to use (default is "4.1").</param>
/// <param name="serviceHostName">The host name of the service (optional).</param>
/// <returns>A task representing the asynchronous operation, which returns the HTTP response message.</returns>
Task<HttpResponseMessage> InvokeAsync(
Models.Connection connection,
string path,
Expand All @@ -19,6 +66,21 @@ Task<HttpResponseMessage> InvokeAsync(
string apiVersion = "4.1",
string serviceHostName = null);

/// <summary>
/// Invokes an Azure DevOps REST API asynchronously.
/// </summary>
/// <typeparam name="T">The type of the response object.</typeparam>
/// <param name="connection">The connection information.</param>
/// <param name="path">The path of the API endpoint.</param>
/// <param name="method">The HTTP method to use (default is "GET").</param>
/// <param name="body">The request body (default is null).</param>
/// <param name="requestContentType">The content type of the request (default is "application/json").</param>
/// <param name="responseContentType">The content type of the response (default is "application/json").</param>
/// <param name="additionalHeaders">Additional headers to include in the request (default is null).</param>
/// <param name="queryParameters">Query parameters to include in the request (default is null).</param>
/// <param name="apiVersion">The version of the API to use (default is "4.1").</param>
/// <param name="serviceHostName">The host name of the service (default is null).</param>
/// <returns>A task representing the asynchronous operation, which returns the response object.</returns>
Task<T> InvokeAsync<T>(
Models.Connection connection,
string path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public CmdletBase CurrentCmdlet
}
}

public string CurrentCommand => CurrentCmdlet.DisplayName;
public string CurrentCommand => CurrentCmdlet.CmdletDisplayName;

public string CurrentCommandLine => CurrentCmdlet.MyInvocation.Line.Trim();

Expand Down
Loading

0 comments on commit 645b1fd

Please sign in to comment.