/*******************************************************************************
* Copyright 2012-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
* this file except in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file.
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
* *****************************************************************************
*
* AWS Tools for Windows (TM) PowerShell (TM)
*
*/
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Amazon.PowerShell.Common
{
///
///
/// Returns the name of the AWS services supported by the current version of AWS Tools for PowerShell, optionally restricting
/// the scope of the search to a specific service which can be identified using one or more words from the service name or the
/// prefix applied to the nouns of cmdlets belonging to the service.
///
///
/// If no match is made, no data is output.
///
///
[Cmdlet("Get", "AWSService")]
[AWSCmdlet("Returns the name of the AWS services supported by the current version of AWS Tools for PowerShell, optionally restricting"
+ " the scope of the search to a specific service which can be identified using one or more words from the service name or the"
+ " prefix applied to the nouns of cmdlets belonging to the service."
)]
[OutputType(typeof(PSObject))]
[AWSCmdletOutput("PSObject", "A collection of zero or more objects listing AWS services supported by the current version of AWS Tools for PowerShell.")]
public class GetAWSServiceCmdlet : BaseCmdlet
{
///
///
/// Matches the full or partial term supplied to the parameter value, which can be the service prefix
/// (for example 'EC2') or one or more terms from the service name (for example 'compute' or 'compute
/// cloud').
///
///
/// When partial names are used (as opposed to a prefix code) all services
/// for which a match can be found are used to assist in the cmdlet results. A
/// regular expression can always be supplied for the parameter value.
///
///
[Parameter(Position = 0, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true)]
public string Service { get; set; }
protected override void ProcessRecord()
{
base.ProcessRecord();
var services = GetFilteredServices(Service)
.Select(service => ConvertToPSObject(service));
if (services.Any())
WriteObject(services, true);
else
WriteVerbose("The specified parameters did not match any service.");
}
internal class ServiceInfo
{
public string Name;
public string Description;
public string CmdletNounPrefix;
public string ModuleName;
public string SDKAssemblyName;
public string SDKAssemblyVersion;
public XElement XmlElement;
}
internal static IEnumerable GetServices()
{
using (var compressedStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Amazon.PowerShell.CmdletsList.dat"))
using (var stream = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var reader = new System.IO.StreamReader(stream))
{
var doc = XElement.Load(reader);
foreach (var service in doc.Elements("Service"))
{
var serviceName = service.Attribute("Name");
yield return new ServiceInfo
{
Name = service.Attribute("Name").Value,
Description = service.Attribute("Description").Value,
CmdletNounPrefix = service.Attribute("CmdletNounPrefix").Value,
ModuleName = service.Attribute("ModuleName").Value,
SDKAssemblyName = service.Attribute("SDKAssemblyName").Value,
SDKAssemblyVersion = service.Attribute("SDKAssemblyVersion").Value,
XmlElement = service
};
foreach (var cmdlet in service.Elements("Cmdlet"))
{
var cmdletName = cmdlet.Attribute("Name");
}
}
}
}
internal static IEnumerable GetFilteredServices(string serviceName)
{
var services = GetServices();
if (serviceName != null)
{
services = FilterByServicePrefixOrName(services, serviceName);
}
return services.OrderBy(service => service.ModuleName);
}
private static IEnumerable FilterByServicePrefixOrName(IEnumerable services, string nameFilter)
{
var matchingServices = services.Where(service => service.CmdletNounPrefix.Equals(nameFilter, StringComparison.OrdinalIgnoreCase)).ToArray();
if (matchingServices.Length == 0)
{
var regex = new Regex(nameFilter, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
matchingServices = services.Where(service => regex.IsMatch(service.Name) || regex.IsMatch(service.Description)).ToArray();
}
return matchingServices;
}
private static PSObject ConvertToPSObject(ServiceInfo service)
{
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("Service", service.Name));
result.Properties.Add(new PSNoteProperty("CmdletNounPrefix", service.CmdletNounPrefix));
#if MODULAR
result.Properties.Add(new PSNoteProperty("ModuleName", service.ModuleName));
#endif
result.Properties.Add(new PSNoteProperty("SDKAssemblyVersion", service.SDKAssemblyVersion));
result.Properties.Add(new PSNoteProperty("ServiceName", service.Description));
return result;
}
}
///
///
/// Returns the name of the cmdlet that invokes a named Amazon Web Services service operation, optionally restricting the
/// scope of the search to a specific service which can be identified using one or more words from the service name or the
/// prefix applied to the nouns of cmdlets belonging to the service.
///
///
/// Returns the names and corresponding service operations for a specific Amazon Web Services service which can be identified
/// using one or more words from the service name or the prefix applied to the nouns of cmdlets belonging to the service.
///
///
/// If no match is made, no data is output.
///
///
[Cmdlet("Get", "AWSCmdletName", DefaultParameterSetName = ByApiOperationOrServiceParameterSet)]
[AWSCmdlet("Searches for cmdlets that invoke a Amazon Web Services service operation, map to an AWS CLI command, or lists all cmdlets"
+ " that belong to a service identified by one or more words in its name or its cmdlet noun prefix. If no service name or pattern is given all service cmdlets are output."
)]
[OutputType(typeof(PSObject))]
[AWSCmdletOutput("PSObject",
"A collection of zero or more objects listing cmdlets that implement the specified operation, map to the AWS CLI command"
+ " or belong to the specified service.")]
public class GetCmdletNameCmdlet : BaseCmdlet
{
public const string ByApiOperationOrServiceParameterSet = "QueryApiOperationOrService";
public const string ByAwsCliCommandParameterSet = "FromAwsCliCommand";
///
///
/// The name of the service operation (api) to search for. If not further restricted by
/// service prefix or service name, all cmdlets across all services are
/// inspected for a matching operation.
///
///
/// By default the value supplied for this parameter is treated as a simple whole-word pattern
/// to match. If the -MatchWithRegex switch is set the value is used as a regular expression.
/// In both cases the search is case-insensitive/invariant culture.
///
///
[Parameter(ParameterSetName = ByApiOperationOrServiceParameterSet, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public string ApiOperation { get; set; }
///
///
/// The name of the cmdlet to search for. If not further restricted by
/// service prefix or service name, all cmdlets across all services are
/// inspected.
///
///
/// By default the value supplied for this parameter is treated as a simple whole-word pattern
/// to match. If the -MatchWithRegex switch is set the value is used as a regular expression.
/// In both cases the search is case-insensitive/invariant culture.
///
///
[Parameter(ParameterSetName = ByApiOperationOrServiceParameterSet, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public string CmdletName { get; set; }
///
/// If set, the value supplied for the ApiOperation or CmdletName parameter is assumed to be a
/// regular expression. By default, the value supplied for ApiOperation or CmdletName are treated
/// as a simple case-insensitive whole-word pattern to match (the cmdlet will surround the value
/// with ^ and $ tokens automatically). If the switch is set no modification of the supplied value
/// is performed.
///
[Parameter(ParameterSetName = ByApiOperationOrServiceParameterSet, ValueFromPipelineByPropertyName = true)]
public SwitchParameter MatchWithRegex { get; set; }
///
///
/// Restricts the search to the cmdlets belonging to services that match the full or
/// partial term supplied to the parameter value, which can be the service prefix
/// (for example 'EC2') or one or more terms from the service name (for example
/// 'compute' or 'compute cloud').
///
///
/// When partial names are used (as opposed to a prefix code) all services
/// for which a match can be found are used to assist in the cmdlet results. A
/// regular expression can always be supplied for the parameter value.
///
///
/// If this is the only parameter supplied to the cmdlet, the output will list all
/// of the cmdlets belonging to the services matching the search term, together
/// with the corresponding service operation names. Note that for services with 'helper'
/// cmdlets that do not invoke a particular service operation the ServiceOperation member
/// for the cmdlet in the resulting output is left blank.
///
///
[Parameter(ParameterSetName = ByApiOperationOrServiceParameterSet, ValueFromPipelineByPropertyName = true)]
public string Service { get; set; }
///
///
/// The AWS CLI command to match. For example 'aws ec2 describe-instances'.
///
/// The cmdlet will make a best-effort to identify the owning service and the operation
/// name by parsing the command using known conventions for the AWS CLI command format.
/// The 'aws' prefix may be omitted and any AWS CLI options (identified by the prefix
/// characters --) are skipped when parsing the value to identify the service code and
/// operation name elements.
///
[Parameter(ParameterSetName = ByAwsCliCommandParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)]
[Obsolete("This parameter is deprecated and will be removed in a future version. Use Service and ApiOperation instead.")]
public string AwsCliCommand { get; set; }
private class CmdletInfo
{
public string Name;
public string[] Operations;
}
private static IEnumerable GetCmdlets(GetAWSServiceCmdlet.ServiceInfo service)
{
foreach (var cmdlet in service.XmlElement.Elements("Cmdlet"))
{
var cmdletName = cmdlet.Attribute("Name");
yield return new CmdletInfo
{
Name = cmdlet.Attribute("Name").Value,
Operations = cmdlet.Attribute("Operations").Value.Split(',')
};
}
}
protected override void ProcessRecord()
{
base.ProcessRecord();
#pragma warning disable CS0618 //A class member was marked with the Obsolete attribute
string awsCliServiceName = null;
string awsCliOperationName = null;
if (AwsCliCommand != null)
{
ParseAwsCliCommand(AwsCliCommand, out awsCliServiceName, out awsCliOperationName);
if (string.IsNullOrEmpty(awsCliServiceName) || string.IsNullOrEmpty(awsCliOperationName))
{
ThrowArgumentError("Unable to extract service and/or operation name from command. Expected text format of 'aws [options] '.", this.AwsCliCommand);
}
WriteVerbose($"Searching for {awsCliOperationName} in service {awsCliServiceName}");
}
#pragma warning restore CS0618
var services = GetAWSServiceCmdlet.GetFilteredServices(awsCliServiceName ?? Service);
var results = new List();
Regex operationNameRegex = null;
if (awsCliOperationName != null)
{
operationNameRegex = new Regex($"^{awsCliOperationName}$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
}
if (ApiOperation != null)
{
operationNameRegex = new Regex(MatchWithRegex.IsPresent ? ApiOperation : $"^{ApiOperation}$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
}
Regex cmdletNameRegex = null;
if (CmdletName != null)
{
cmdletNameRegex = new Regex(MatchWithRegex.IsPresent ? CmdletName : $"^{CmdletName}$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
}
foreach (var service in services)
{
var cmdlets = GetCmdlets(service);
if (cmdletNameRegex != null)
{
cmdlets = cmdlets.Where(cmdlet => cmdletNameRegex.IsMatch(cmdlet.Name));
}
if (operationNameRegex != null)
{
cmdlets = cmdlets.Where(cmdlet => cmdlet.Operations.Any(operation => operationNameRegex.IsMatch(operation)));
}
results.AddRange(cmdlets
.OrderBy(cmdlet => cmdlet.Name)
.Select(cmdlet => ConvertToPSObject(service, cmdlet)));
}
if (results.Any())
WriteObject(results, true);
else
WriteVerbose("The specified parameters did not match any service.");
}
private static PSObject ConvertToPSObject(GetAWSServiceCmdlet.ServiceInfo service, CmdletInfo cmdlet)
{
var result = new PSObject();
result.Properties.Add(new PSNoteProperty("CmdletName", cmdlet.Name));
result.Properties.Add(new PSNoteProperty("ServiceOperation", string.Join(";", cmdlet.Operations)));
result.Properties.Add(new PSNoteProperty("ServiceName", service.Description));
#if MODULAR
result.Properties.Add(new PSNoteProperty("ModuleName", service.ModuleName));
#endif
return result;
}
///
/// Parses a typical aws cli command to extract the service name and operation. Some flexibility is
/// allowed, to make it easy for users who are transcoding a cli sample to PowerShell.
///
///
/// Cli command to parse; as this was a parameter value that triggered a parameter set, we know it is not null.
///
///
///
static void ParseAwsCliCommand(string command, out string serviceName, out string operationName)
{
serviceName = null;
operationName = null;
// Usual format: aws [options]
// Also allow for 'aws' to be dropped. Options are expected to conform to -- prefix format.
var cmdParts = command.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var i = 0;
if (cmdParts.Length == 0)
return;
if (cmdParts[0].Equals("aws", StringComparison.OrdinalIgnoreCase))
i = 1;
while (i < cmdParts.Length && operationName == null)
{
if (!cmdParts[i].StartsWith("--"))
{
if (serviceName == null)
serviceName = cmdParts[i];
else
operationName = cmdParts[i].Trim().Replace("-", "");
}
i++;
}
}
}
}