using System; using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; using System.IO; using System.Diagnostics; using System.Text; using System.Xml; using AWSPowerShellGenerator.Analysis; using System.ComponentModel; using AWSPowerShellGenerator.Generators; using System.Management.Automation; namespace AWSPowerShellGenerator.ServiceConfig { /// /// Collection of service ConfigModel objects /// public class ConfigModelCollection { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("VerbMappings")] public List VerbMappingsList = new List(); Dictionary _verbMappings; /// /// Cross-service verb remaps /// [XmlIgnore] public Dictionary VerbMappings { get { if (_verbMappings == null) _verbMappings = VerbMappingsList.ToDictionary(m => m.From, m => m.To); return _verbMappings; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("OperationNameMappings")] public List OperationNameMappingsList = new List(); Dictionary _operationNameMappings; /// /// Cross-service operation name remaps /// [XmlIgnore] public Dictionary OperationNameMappings { get { if (_operationNameMappings == null) _operationNameMappings = OperationNameMappingsList.ToDictionary(m => m.From, m => m.To); return _operationNameMappings; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("NounMappings")] public List NounMappingsList = new List(); Dictionary _nounMappings; /// /// Cross-service noun remaps /// [XmlIgnore] public Dictionary NounMappings { get { if (_nounMappings == null) _nounMappings = NounMappingsList.ToDictionary(m => m.From, m => m.To); return _nounMappings; } } /// /// Global list of member types that we should not attempt to flatten to 'typename_membername' during /// codegen. The global list should really be restricted to just types external to AWS services - /// non-flatting types at the service level can be declared in the service config files, and are fused /// with this collection automatically. /// [XmlArray("TypesNotToFlatten")] [XmlArrayItem("Type")] public List TypesNotToFlatten { get; set; } = new List(); /// /// List of cmdlet output properties that are considered metadata, this is used to fill in the /// list of metadata properties for an operation during its first automated configuration. /// We include common pagination properties here in order to have them show up for in the configuration /// and help identifying cmdlets with misconfigured pagination /// [XmlArray("MetadataProperties")] [XmlArrayItem("Property")] public List MetadataPropertyNames { get; set; } /// /// List of cmdlet parameters that are considered metadata and moved to the end of the parameters list /// [XmlArray("MetadataParameters")] [XmlArrayItem("Parameter")] public List MetadataParameterNames { get; set; } /// /// Collection of xml config files that the generator should operate on /// [XmlArray] [XmlArrayItem("Path")] public List Configs { get; set; } = new List(); /// /// Collection of deserialized service configuration models indexed by C2jFilename /// [XmlIgnore] public Dictionary ConfigModels { get; set; } = new Dictionary(); /// /// Global definition of types our cmdlets can accept as 'InputObject' /// from the pipeline and the type member to cmdlet parameter mapping(s) /// that should be performed if the type is detected /// [XmlArray("InputObjectMappingRules")] [XmlArrayItem("InputObjectMapping")] public List InputObjectMappingRulesList { get; set; } Dictionary _inputObjectMappingRules; [XmlIgnore] public Dictionary InputObjectMappingRules { get { if (_inputObjectMappingRules == null) _inputObjectMappingRules = InputObjectMappingRulesList.ToDictionary(a => a.MappingRefName, a => a); return _inputObjectMappingRules; } } [XmlArray("IncludeLibraries")] public List IncludeLibrariesList = new List(); [XmlArray("CommonModuleAliases")] [XmlArrayItem("AliasSet")] public List CommonModuleAliasesList = new List(); Dictionary> _commonModuleAliases; [XmlIgnore] public Dictionary> CommonModuleAliases { get { return _commonModuleAliases ?? (_commonModuleAliases = CommonModuleAliasesList.ToDictionary(a => a.Cmdlet, a => a.Aliases)); } } /// /// Loads the core config.xml file and the indicated service configuration files it contains. /// /// /// /// public static ConfigModelCollection LoadAllConfigs(string configurationsFolder, bool verbose = false) { var manifestConfigFile = Path.GetFullPath(Path.Combine(configurationsFolder, "Configs.xml")); if (verbose) Console.WriteLine("...loading configuration manifest {0}", manifestConfigFile); var manifestConfig = DeserializeModelCollection(manifestConfigFile); var serviceConfigurationsFolder = Path.Combine(configurationsFolder, CmdletGenerator.ServiceConfigFoldername); var serviceConfigurations = Directory.GetFiles(serviceConfigurationsFolder, "*.xml", SearchOption.TopDirectoryOnly); foreach (var configFile in serviceConfigurations.OrderBy(x => x)) { if (verbose) Console.WriteLine("...loading service configuration {0}", configFile); try { var configModel = DeserializeModel(configFile); manifestConfig.ConfigModels.Add(configModel.C2jFilename, configModel); } catch (Exception e) { throw new Exception("Failed to deserialize config model " + configFile, e); } } return manifestConfig; } private static ConfigModelCollection DeserializeModelCollection(string fileName) { try { var serializer = new XmlSerializer(typeof(ConfigModelCollection)); using (var fs = new FileStream(fileName, FileMode.Open)) { using (var reader = new StreamReader(fs)) { return (ConfigModelCollection)serializer.Deserialize(reader); } } } catch (Exception e) { throw new InvalidDataException("Unable to retrieve content for file " + fileName, e); } } private static ConfigModel DeserializeModel(string fileName) { try { var serializer = new XmlSerializer(typeof(ConfigModel)); using (var fs = new FileStream(fileName, FileMode.Open)) { using (var reader = new StreamReader(fs)) { return (ConfigModel)serializer.Deserialize(reader); } } } catch (Exception e) { throw new InvalidDataException("Unable to retrieve content for file " + fileName, e); } } } /// /// Defines a single input object member to cmdlet parameter mapping rule /// public class MappingRule { /// /// The name of the member in the cast input object the cmdlet should extract /// [XmlAttribute] public string MemberName { get; set; } /// /// The cmdlet parameter to which the extracted member should be applied /// [XmlAttribute] public string ParamName { get; set; } /// /// InputObject parameter help documentation for this rule /// [XmlAttribute] public string HelpDescription { get; set; } } /// /// Defines type conversion mapping rules for a given type /// public class InputObjectMapping { /// /// Name that can be used to reference this type mapping from other config files /// [XmlAttribute("RefName")] public string MappingRefName { get; set; } /// /// Returns true if this entry is a reference to a global map entry via /// MappingRefName /// [XmlIgnore] public bool IsGlobalReference { get { return string.IsNullOrEmpty(this.TypeName); } } /// /// The CLR type that these map rules apply to /// [XmlAttribute("Type")] public string TypeName { get; set; } [XmlArray("MappingRules")] [XmlArrayItem("MappingRule")] public List MappingRules { get; set; } } /// /// Defines the configuration for a given service model that is passed to the generator /// public class ConfigModel { #region Configuration Properties /// /// Manually increment this number in the xml configuration file when /// making heavy changes to the format or content of the configuration. /// It will prevent automatically applying overrides if they present /// an older version. /// public int FileVersion = 0; /// /// /// If specified, allows us to skip reflecting over the service client /// to generate cmdlets but still process other data in the config /// (eg legacy aliases). /// /// /// We currently use this for CloudSearchDomain which has non- /// standard client constructors and all of its operations /// excluded from codegen. Due to originally having plural cmdlet /// names, we want to take advantage of alias processing but none /// of the rest of the generation process. /// /// public bool SkipCmdletGeneration; /// /// Base name of the corresponding C2j file containing the api model. We /// only use this in transition to the new C2j-based PowerShell generator /// framework. /// public string C2jFilename; /// /// Assembly name the AWSSDK or AWSPowerShell prefix is not included /// public string AssemblyName = string.Empty; /// /// Service-specific 'tag' to be prefixed onto each noun and alias /// public string ServiceNounPrefix = string.Empty; /// /// The marketing name of the service. /// public string ServiceName = string.Empty; /// /// Typename of the interface implemented by the service client /// public string ServiceClientInterface = string.Empty; /// /// Typename of the service client type to be instantiated /// public string ServiceClient = string.Empty; public string ServiceClientConfig { get { var clientIndex = ServiceClient.LastIndexOf("Client"); if (clientIndex < 0) throw new InvalidOperationException("Cannot determine config name for client " + ServiceClient); return (ServiceClient.Substring(0, clientIndex) + "Config"); } } /// /// The root namespace of the service in the SDK, eg Amazon.EC2 /// public string ServiceNamespace => "Amazon." + AssemblyName; /// /// The Guid assigned to the service module. /// public string ServiceModuleGuid = Guid.NewGuid().ToString(); /// /// Default region to use for the cmdlets if Region isn't passed in. /// public string DefaultRegion; /// /// For S3 only, switch instructs generator to treat response object as result object. /// [XmlIgnore] public bool IsS3 { get { return string.Equals(ServiceClientInterface, "IAmazonS3", StringComparison.Ordinal); } } /// /// Optional name of a parameter across one or more cmdlets that we should apply /// PipelineParameter to, unless the service operation being generated has an /// override. /// public string PipelineParameter = string.Empty; public AutoIteration AutoIterate = null; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("VerbMappings")] public List VerbMappingsList = new List(); Dictionary _verbMappings; /// /// Service-specific overrides of verb remaps /// [XmlIgnore] public Dictionary VerbMappings { get { return _verbMappings ?? (_verbMappings = VerbMappingsList.ToDictionary(m => m.From, m => m.To)); } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("NounMappings")] public List NounMappingsList = new List(); Dictionary _nounMappings; /// /// Service-specific noun remaps /// [XmlIgnore] public Dictionary NounMappings { get { return _nounMappings ?? (_nounMappings = NounMappingsList.ToDictionary(m => m.From, m => m.To)); } } /// /// Collection of service-global parameter customizations. /// [XmlArray("Params")] [XmlArrayItem("Param")] public List CustomParametersList = new List(); private Dictionary _customParameters; [XmlIgnore] public IDictionary CustomParameters { get { if (_customParameters == null) { _customParameters = CustomParametersList.ToDictionary(p => p.Name, p => p); } return _customParameters; } } public Param FindCustomParameterData(string parameterName) { if (CustomParameters.ContainsKey(parameterName)) return CustomParameters[parameterName]; return null; } public bool ShouldExcludeParameter(string parameterName) { return ShouldExcludeParameter(parameterName, CustomParameters); } internal static bool ShouldExcludeParameter(string parameterName, IDictionary customizedParameters) { if (customizedParameters == null) return false; // we use a convention of a trailing _ on the parameter name to // allow us to exclude a group of parameters by common prefix foreach (var p in customizedParameters) { var param = p.Value; if (param.Exclude) { if (param.Name.EndsWith("_", StringComparison.Ordinal) && parameterName.StartsWith(param.Name, StringComparison.Ordinal)) return true; if (param.Name.Equals(parameterName, StringComparison.Ordinal)) return true; } } return false; } /// /// Additional aliases defined in the config, beyond the automatic aliases we generate. /// These go into awsaliases.ps1. /// Key is the cmdlet name; value is the alias (including parameters) /// [XmlArray("CustomAliases")] [XmlArrayItem("AliasSet")] public List CustomAliasesList = new List(); Dictionary> _customAliases; [XmlIgnore] public Dictionary> CustomAliases { get { return _customAliases ?? (_customAliases = CustomAliasesList.ToDictionary(a => a.Cmdlet, a => a.Aliases)); } } /// /// Legacy alias entries for custom, hand-maintained cmdlets for a service. /// These entries are used to ensure the aliases get emitted into the /// AWSPowerShellLegacyAliases.psm1 file and the overall module manifest. Hand- /// maintained cmdlets that need a legacy alias should also have a LegacyAlias /// attribute entry added to their AWSCmdletAttribute - this ensures that an table /// of contents entry is generated for the alias in the web doc generator. /// NOTE: Only use this collection in config files to provide aliases for custom /// cmdlets. For generatable service operations, use the LegacyAlias attribute /// on the respective ServiceOperation element. /// Key is the current cmdlet name; value is the backwards-compatible alias /// (there should only ever be one alias) /// [XmlArray("LegacyAliases")] [XmlArrayItem("AliasSet")] public List LegacyAliasesList = new List(); Dictionary> _legacyAliases; [XmlIgnore] public Dictionary> LegacyAliases { get { return _legacyAliases ?? (_legacyAliases = LegacyAliasesList.ToDictionary(a => a.Cmdlet, a => a.Aliases)); } } /// /// List of common service-specific output properties to be considered as metadata, this /// is used to fill in the list of metadata properties for an operation during its first /// automated configuration /// [XmlArray("MetadataProperties")] [XmlArrayItem("Property")] public List MetadataPropertyNames { get; set; } public const string ParamEmitterComplexKeyFormat = "{0}#{1}"; // type#propname [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("ParamEmitters")] [XmlArrayItem("ParamEmitter")] public List ParamEmittersList = new List(); Dictionary _typeSpecificParamEmitters; /// /// Returns the service-specific custom parameter emitters that are tied to a /// specific type. These parameters are only injected on cmdlets that have a /// parameter matching the specified type. /// [XmlIgnore] public Dictionary TypeSpecificParamEmitters { get { if (_typeSpecificParamEmitters == null) { _typeSpecificParamEmitters = new Dictionary(); foreach (var p in ParamEmittersList) { if (p.IsGlobalInjectionEmitter) continue; var key = !string.IsNullOrEmpty(p.ParamName) ? string.Format(ParamEmitterComplexKeyFormat, p.ParamType, p.ParamName) : p.ParamType; _typeSpecificParamEmitters.Add(key, p.EmitterType); } } return _typeSpecificParamEmitters; } } List _globalInjectionParamEmitters; /// /// Returns the collection of param emitters that are configured globally /// for a service. These parameters are injected into every cmdlet unless /// the cmdlet is configured in the exclusion list for an emitter. /// [XmlIgnore] public List GlobalInjectionParamEmitters { get { if (_globalInjectionParamEmitters == null) { _globalInjectionParamEmitters = new List(); foreach (var p in ParamEmittersList) { if (p.IsGlobalInjectionEmitter) _globalInjectionParamEmitters.Add(p); } } return _globalInjectionParamEmitters; } } private List _serviceOperationsList = new List(); [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("ServiceOperations")] [XmlArrayItem("ServiceOperation")] public List ServiceOperationsList { get { return _serviceOperationsList; } set { _serviceOperationsList = value; _serviceOperations = null; } } Dictionary _serviceOperations; [XmlIgnore] public Dictionary ServiceOperations { get { if (_serviceOperations == null) _serviceOperations = ServiceOperationsList.ToDictionary(a => a.MethodName + "Async", a => a); return _serviceOperations; } } public string GetServiceCmdletClassName(bool usingAnonymousAuth) { if (usingAnonymousAuth) return string.Concat("Anonymous", ServiceClient); return ServiceClient; } private bool? _requiresAnonymousServiceCmdletClass = null; [XmlIgnore] public bool RequiresAnonymousServiceCmdletClass { get { if (_requiresAnonymousServiceCmdletClass == null) { _requiresAnonymousServiceCmdletClass = false; foreach (var so in ServiceOperationsList) { if (so.RequiresAnonymousAuthentication) { _requiresAnonymousServiceCmdletClass = true; break; } } } return _requiresAnonymousServiceCmdletClass.Value; } } /// /// Service-specific collection of type names that will not be flattened /// during parameter generation. This collection is fused with the global /// collection automatically during codegen. /// [XmlArray("TypesNotToFlatten")] [XmlArrayItem("Type")] public List TypesNotToFlatten { get; set; } = new List(); #endregion #region Generated Output Properties [XmlIgnore] public ArgumentCompleterDetails ArgumentCompleters { get; } = new ArgumentCompleterDetails(); [XmlIgnore] public Dictionary AdvancedCmdlets { get; } = new Dictionary(StringComparer.CurrentCultureIgnoreCase); [XmlIgnore] public IEnumerable SDKDependencies { get; set; } [XmlIgnore] public readonly List AnalysisErrors = new List(); [XmlIgnore] public System.Reflection.Assembly Assembly; #endregion public ConfigModel() { } public void Serialize(string filePath) { Console.WriteLine("Updating configuration file for service {0}, file {1}", ServiceName, filePath); try { var serializer = new XmlSerializer(typeof(ConfigModel)); var writerSettings = new XmlWriterSettings { Encoding = new UTF8Encoding(false), Indent = true, IndentChars = " " }; using (var writer = XmlWriter.Create(filePath, writerSettings)) { serializer.Serialize(writer, this); } } catch (Exception e) { throw new InvalidDataException("Unable to serialize updated model to " + filePath, e); } } } /// /// Information about handwritten cmdlets. These are filled in by AdvancedCmdletScanner /// public class AdvancedCmdletInfo { public List OperationNames = new List(); } /// /// Encapsulates all the generation info for a given service operation /// public class ServiceOperation { [XmlAttribute] public string MethodName; /// /// The property of the operation's output class to be returned from the cmdlet /// (unless the user specifies differently using the -Select parameter). /// A value of null means that the operation doens't return any non-metadata /// property,.the cmdlet will return void to allow choosing a proper OutputProperty /// value at a later date without breaking backward compatibility (in case return /// properties are added to the operation). /// A value of "" means that the whole service response should be returned. /// [XmlAttribute] public string OutputProperty; /// /// Set to the name of a member of the response class that contains the true output /// of the call (happens when the SDK wraps the output into a nested member, instead /// of hosting it in the response class itself - it does this with some SWF response /// types). The generator will use the indicated member when it does Output and /// auto-pagination inspection instead of using the outer response class. The /// semantics for 'Output' will therefore apply to the wrapping member, not the response /// class. /// [XmlAttribute] public string OutputWrapper; [XmlAttribute("Verb")] public string RequestedVerb = string.Empty; [XmlAttribute("Noun")] public string RequestedNoun = string.Empty; [XmlArray("Params")] [XmlArrayItem("Param")] public List CustomParametersList = new List(); private Dictionary _customParameters; [XmlIgnore] public IDictionary CustomParameters { get { if (_customParameters == null) { _customParameters = CustomParametersList?.ToDictionary(param => param.Name, param => param) ?? new Dictionary(); } return _customParameters; } } public bool ShouldExcludeParameter(string parameterName) { return ConfigModel.ShouldExcludeParameter(parameterName, CustomParameters); } public Param FindCustomParameterData(string parameterName) { if (CustomParameters.ContainsKey(parameterName)) return CustomParameters[parameterName]; return null; } /// /// If set, the operation is excluded from generation. /// [XmlAttribute] [DefaultValue(false)] public bool Exclude; /// /// Set true to suppresses generation of the SupportsShouldProcess /// attribution and code pattern foir cmdlets that don't change /// system state but whose verb is not on the 'ignore' list. /// [XmlAttribute] [DefaultValue(false)] public bool IgnoreSupportsShouldProcess; /// /// If the cmdlet verb is one that needs SupportsShouldProcess /// attributing and the request class has more than one property, /// indicates the name of the cmdlet property that we will prompt /// for confirmation on. /// [XmlAttribute] public string ShouldProcessTarget; /// /// If the resources the cmdlet is going to operate on can't be /// identified to the point of being able to include them in a /// confirmation prompt, set this true to indicate that the /// generator should not error on the failure to have an explicit /// target be set in the config. /// [XmlAttribute] [DefaultValue(false)] public bool AnonymousShouldProcessTarget; /// /// If the cmdlet verb is one that needs SupportsShouldProcess /// attributing, optionally contains a more readable display-format /// noun for the type of object we are prompting for confirmation on /// (eg 'Customer Gateway'). If not specified, the cmdlet noun is used /// in any confirmation messages we generate. /// [XmlAttribute] public string ShouldProcessMsgNoun; /// /// The type of anonymous authentication permitted for a given operation. /// Most operations require user authentication, some allow it to be optional /// (via an injected SwitchParameter that the user can specify) and others /// always operate anonymously. /// public enum AnonymousAuthenticationMode { Never, Optional, Always } /// /// If set 'Always;, the operation is unauthenticated and will be generated to use a /// service cmdlet base class that is configured to use AnonymousCredentials (eventually /// we could detect this from attribution on the operation in the SDK or C2j model). /// [XmlAttribute] [DefaultValue(AnonymousAuthenticationMode.Never)] public AnonymousAuthenticationMode AnonymousAuthentication = AnonymousAuthenticationMode.Never; [XmlIgnore] public bool RequiresAnonymousAuthentication { get { return AnonymousAuthentication == AnonymousAuthenticationMode.Always; } } /// /// Set of parameter names, ;-delimited, that should have Position data emitted /// in order of definition (starting at 0). PS recommendation is no more than 5 /// per cmdlet. This list will be with prefixed with the cross-operation set /// of PositionalParameters. /// [XmlAttribute] public string PositionalParameters; [XmlIgnore] public string[] PositionalParametersList { get { return PositionalParameters?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0]; } set { PositionalParameters = value == null ? null : string.Join(";", value); } } /// /// Name of the single parameter (single per PS convention) that should be /// tagged as accepting pipeline input by value if the service global /// PipelineParameter setting does not apply. /// /// /// This differs from pipeline input by matching property name, of which /// more than one param can be tagged. /// [XmlAttribute] public string PipelineParameter = string.Empty; /// /// If set true, the cmdlet can be generated without triggering an /// error due to a missing PipelineParameter attribute. /// [XmlAttribute] public bool NoPipelineParameter; /// /// Custom pass-thru expression override to use instead of automatically /// emitted the parameter marked for pipeline input to the output, for /// operations that have an output type of 'void'. /// public PassThruOverride PassThru; /// /// Overrides the service level iteration settings for an operation, for /// services that use inconsistent markers etc across their apis /// public AutoIteration AutoIterate; public enum LegacyPaginationType { Default, DisablePagination, UseEmitLimit } /// /// For AWSPowerShell and AWSPowerShell.NetCore, emit legacy pagination code using EmitLimit /// [XmlAttribute] [DefaultValue(LegacyPaginationType.Default)] public LegacyPaginationType LegacyPagination; [XmlAttribute] public string LegacyPaginationCountParameter; /// /// /// If specified, the legacy cmdlet name for which a Set-Alias entry will be added to /// the AWSPowerShellLegacyAliases.psm1 file during generation. /// /// /// This mechanism allows us to rename cmdlets going forward without introducing a /// breaking change (the psm1 file is auto-loaded when our module loads). The value /// of the attribute is the old name of the cmdlet - this will be used as the -Name /// value to the Set-Alias cmdlet. The -Value for Set-Alias will be the current name /// of the cmdlet. /// /// [XmlAttribute] public string LegacyAlias; /// /// Operation-specific collection of type names that will not be flattened /// during parameter generation. This collection is fused with the service and global /// collections automatically during codegen. /// [XmlArray("TypesNotToFlatten")] [XmlArrayItem("Type")] public List TypesNotToFlatten { get; set; } = new List(); /// /// If set, this message is used for the Obsolete attribute. /// [XmlAttribute] public string ReplacementObsoleteMessage; /// /// Must be one of the values of System.Management.Automation.ConfirmImpact or null /// [XmlAttribute] public string ConfirmImpact; /// /// Default parameter set for the cmdlet. /// [XmlAttribute] public string DefaultParameterSet; #region Data constructed during generation /// /// The analyzer instance and its results for this op /// [XmlIgnore] public OperationAnalyzer Analyzer; /// /// The verb we decided, or were directed, to use for the cmdlet /// [XmlIgnore] public string SelectedVerb; /// /// The noun we decided, or were directed, to use for the cmdlet /// [XmlIgnore] public string SelectedNoun; /// /// The noun from the split-apart service method name; for use as /// the noun in confirmation messages if the cmdlet needs to implement /// the ShouldSupportProcess pattern /// [XmlIgnore] public string OriginalNoun; /// /// Set true once we've encountered the operation config and matched it /// with a method on the service client. If any operations are still false /// when we conclude the service generation, we emit a build fail since it /// indicates we could be building against an out-of-date sdk. /// [XmlIgnore] public bool Processed; /// /// Set when the generator detects a method that is not configured already. /// The generator will take a best-guess attempt to set up a service operation /// entry that can then be adjusted by hand if needed. /// [XmlIgnore] public bool IsAutoConfiguring; /// /// Set when auto-configuring if we detect an SDK 'List' verb. We'll /// auto-remap to 'Get' and then append 'List' to the noun. /// [XmlIgnore] public bool IsRemappedListOperation; [XmlIgnore] public readonly List AnalysisErrors = new List(); #endregion } /// /// Represents a configuration for a parameter emitted as part of a service operation. /// Parameters can be renamed, renamed with an alias, have just a set of aliases applied, /// be configured to be named 'as is' (ie no shortening or singularization) or have a default /// value available if the user skips the parameter. /// public class Param { /// /// Tells us where the customization originated. /// public enum CustomizationOrigin { FromConfig = 0, DuringGeneration } [XmlIgnore] public CustomizationOrigin Origin; /// /// The analyzed (and fully flattened) name of the parameter /// [XmlAttribute] public string Name; /// /// If specified, contains the name that will be emitted for the parameter (ie /// this is the final user-visible name). If not set, the parameter will not be /// renamed. /// [XmlAttribute] public string NewName; /// /// If set false, the parameter will not be automatically renamed (by shortening /// and/or singularization); to change the parameter name in these cases /// a NewName attribute must be used otherwise the original analyzed name will /// be emitted. /// [XmlAttribute] [DefaultValue(true)] public bool AutoRename = true; /// /// If set true, the parameter will be exluded from generation and will not be /// end-user visible. /// [XmlAttribute] [DefaultValue(false)] public bool Exclude = false; /// /// If set false, an alias of the original name will not be applied to the /// parameter (need a flag because an empty list doesn't uniquely define this /// case). /// [XmlAttribute] [DefaultValue(true)] public bool AutoApplyAlias = true; /// /// Sets a default value to be used if the user does not specify the parameter /// to the command. Supports string or scalar (int, float, double) parameters /// only. /// During generation don't use this value, use SimplePropertyInfo.DefaultValue /// instead /// [XmlAttribute] public string DefaultValue; /// /// If specified, contains a set of one or more aliases, ;-delimited, to apply /// for the parameter that have been read from the configuration file. Access this /// collection via the Aliases property, do not modify this string. /// [XmlAttribute(AttributeName = "Alias")] public string AliasesList; /// /// The processed set of aliases for use when emitting code. This collection can /// be updated as we inspect the parameters for a cmdlet. /// [XmlIgnore] public HashSet Aliases { get { return AliasesList == null ? new HashSet() : new HashSet(AliasesList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } set { AliasesList = value == null ? null : string.Join(";", value); } } /// /// If specified, contains a set of one or more parameters, ;-delimited, that cannot /// be used together with the current one. /// [XmlAttribute(AttributeName = "ExclusiveParameters")] public string ExclusiveParametersList; /// /// The processed set of parameters that are not allowed when the current one is /// specified. This is used when emitting code. /// [XmlIgnore] public HashSet ExclusiveParameters { get { return ExclusiveParametersList == null ? new HashSet() : new HashSet(ExclusiveParametersList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } set { ExclusiveParametersList = value == null ? null : string.Join(";", value); } } public enum AutoConversion { None = 0, ToBase64 = 1 } /// /// If set, the cmdlet automatically converts from the source type (string or /// byte array) to a base64 representation required by the service. /// [XmlAttribute] [DefaultValue(AutoConversion.None)] public AutoConversion AutoConvert = AutoConversion.None; /// /// If set, this message is used for the Obsolete attribute. /// [XmlAttribute] public string ReplacementObsoleteMessage; /// /// The ParameterSetName to be used. This is for backward compatibility only. /// [XmlAttribute] public string ParameterSetName; /// /// The parameter is mandatory. This is for backward compatibility only. /// [XmlAttribute] [DefaultValue(false)] public bool Mandatory; /// /// Replacement documentation for this parameter. This is for backward compatibility only. /// [XmlAttribute] [DefaultValue(null)] public string ParameterReplacementDocumentation; } public class AliasSet { [XmlAttribute] public string Cmdlet = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlText] public string AliasesField = string.Empty; [XmlIgnore] public HashSet Aliases { get { return new HashSet(AliasesField.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } } } public class AutoIteration { // set of aliases applied to paging tokens cross-services, to smooth // out service naming differences. We keep the parameter name to help // users who either know the service api or want to reference // documentation. The names are chosen to likely never interfere // with service names. private const string NextAlias = "NextToken"; private const string EmitLimitAlias = "MaxItems"; /// /// The field in the request class that indicates where the service /// should start returning results from /// [XmlAttribute] public string Start; /// /// The field in the response/result class that indicates where the /// next 'page' of results starts from /// [XmlAttribute] public string Next; /// /// For services that allow the user to set a max number of records /// to return; this can be more or less than the service's page size /// (if the service has one) /// [XmlAttribute] public string EmitLimit; /// /// The service's max records per call value; not all services have one /// [XmlAttribute] [DefaultValue(-1)] public int ServicePageSize = -1; /// /// The service requires EmitLimit to be set. The configuration of this /// value was set in order to maintain the existing behavior of the module. /// We have no idea what is the correct value for each service unless we /// test each operation! /// [XmlAttribute] [DefaultValue(false)] public bool PageSizeIsRequired; /// /// Returns the autoiteration settings, if any, for an operation by combining the service level /// settings and any operation-level overrides. For operation level overrides, not all settings /// need to be specified. For operations that have less fields than the service level, specify /// an empty string for the non-present fields otherwise the service level setting will be /// assumed. /// /// The service-level autoiteration settings, if any /// Service-operation overrides, if any /// The combined iteration settings with child settings overriding parent settings where set public static AutoIteration Combine(AutoIteration parentSettings, AutoIteration childSettings) { var chosenSettings = childSettings ?? parentSettings; if (chosenSettings == null) return null; return new AutoIteration { Start = chosenSettings.Start, Next = chosenSettings.Next, ServicePageSize = chosenSettings.ServicePageSize, EmitLimit = chosenSettings.EmitLimit, PageSizeIsRequired = chosenSettings.PageSizeIsRequired }; } /// /// Allows the generator to filter out properties related to iteration; note that the /// 'is truncated' property is allowed through, since it doesn't relate directly /// to paging/page sizes /// /// /// public bool IsIterationParameter(string propertyName) { return propertyName == Start || propertyName == EmitLimit; } /// /// Returns the cross-service alias for the specified iteration parameter (start and max params /// only, 'itrnext' is an internal field) provided the parameter name isn't already the alias. /// /// /// public string GetIterationParameterAlias(string propertyName) { if (propertyName == Start && propertyName != NextAlias) return NextAlias; if (propertyName == EmitLimit && propertyName != EmitLimitAlias) return EmitLimitAlias; return null; } } public class Mapping { public K From = default(K); public V To = default(V); public Mapping() { } public Mapping(K from, V to) { From = from; To = to; } } public class Map { [XmlAttribute] public string From = string.Empty; [XmlAttribute] public string To = string.Empty; public Map() { } public Map(string from, string to) { From = from; To = to; } } public class Library { [XmlAttribute] public string Name = string.Empty; [XmlAttribute] public bool AddAsReference = false; public Library() { } public Library(string name, bool addAsReference) { Name = name; AddAsReference = addAsReference; } } public class ParamEmitter { [XmlAttribute] public string ParamType = string.Empty; [XmlAttribute] public string ParamName = string.Empty; [XmlAttribute] public string EmitterType = string.Empty; [XmlAttribute] public string Exclude = string.Empty; /// /// Globally injected emitters are used to inject arbitrary parameters that don't map to /// model shape types. S3's UseAcceleratedEndpoint and UseDualstackEndpoint are /// examples of these; both are bools (so the type cannot be used as a key in the /// emitter dictionary) and both get added to multiple cmdlets. /// [XmlIgnore] public bool IsGlobalInjectionEmitter { get { return string.IsNullOrEmpty(ParamName) && string.IsNullOrEmpty(ParamType); } } public ParamEmitter() { } public ParamEmitter(string paramType, string emitterType) : this(paramType, string.Empty, emitterType) { } public ParamEmitter(string paramType, string paramName, string emitterType) { ParamType = paramType; ParamName = paramName; EmitterType = emitterType; } } /// /// Contains the custom code expression and documentation for the -PassThru parameter. /// /// If not specified (the default) for a service operation that has an output type of /// 'void', the value passed to the parameter declared as the PipelineParameter will /// be emitted (so long as the user sets the -PassThru switch). /// /// /// If this customization is specified, the assignment to the CmdletOutput's PipelineOutput /// property in the cmdlet executor is done from the code expression supplied as /// Expression. This can be a reference to a request object field (e.g. 'request.Tags') or /// a call to a custom method built as part of an extension class to the cmdlet. /// /// public class PassThruOverride { /// /// The code expression that yields the output from the cmdlet. This can /// be a reference to a member of the SDK request object, or a member of the /// cmdletContext instance or a call to a method implemented in an extension /// class for the cmdlet. /// /// context.Tags /// GetTagOutputFromHere(context.Tags) public string Expression { get; set; } /// /// The type of the object that is output (for collections, this should /// be the collected object type). This is used to augment the OutputType /// attribute on the cmdlet. /// public string Type { get; set; } /// /// 'One liner' documentation describing what is output. The generator will /// automatically append a 'By default, this cmdlet does not generate any output.' /// suffix to follow other cmdlet standards. /// public string Documentation { get; set; } } /// /// Tracks usage of SDK ConstantClass-derived types in parameters so that we /// can generate argument completers for a service. /// public class ArgumentCompleterDetails { /// /// Tracks the members of each ConstantClass-derived type /// private Dictionary> _constantClassMembers = new Dictionary>(); /// /// Tracks the cmdlet references, by parameter name, for a given ConstantClass-derived type /// private Dictionary _constantClassReferences = new Dictionary(); /// /// Returns the ordered collection of ConstantClass-derived type names that were /// found to be referenced during cmdlet generation or inspection of hand-maintained /// cmdlets. /// public IEnumerable ReferencedClasses { get { var l = new List(_constantClassReferences.Keys); l.Sort(); // return ordered so codegen'd file gets consistent layout return l; } } /// /// Returns the collection of parameter and cmdlet references for a ConstantClass-derived /// type. /// /// /// public ConstantClassReferences GetReferencesFor(string constantClassTypename) { ConstantClassReferences refs; if (_constantClassReferences.TryGetValue(constantClassTypename, out refs) && refs != null) return refs; throw new ArgumentException(string.Format("ConstantClass-derived type {0} has not been encountered during generation", constantClassTypename)); } /// /// Returns the collection of members for a ConstantClass-derived type. /// /// /// public IEnumerable GetConstantClassMembers(string constantClassTypename) { IEnumerable members; if (_constantClassMembers.TryGetValue(constantClassTypename, out members) && members != null) return members; throw new ArgumentException(string.Format("ConstantClass-derived type {0} has not been encountered during generation", constantClassTypename)); } /// /// Indicates if the members of the ConstantClass-derived type have already been registered. /// /// /// public bool IsConstantClassRegistered(string constantClassTypeName) { return _constantClassMembers.ContainsKey(constantClassTypeName); } /// /// Registers the members of a ConstantClass-derived type so that we can generate /// an argument completer for the type. /// /// /// public void AddConstantClass(string constantClassTypename, IEnumerable setMembers) { if (_constantClassMembers.ContainsKey(constantClassTypename)) return; _constantClassMembers.Add(constantClassTypename, setMembers); } /// /// Initializes or adds a new cmdlet parameter reference to a ConstantClass-derived /// SDK 'enum' type. /// /// /// /// public void AddConstantClassReference(string constantClassTypename, string parameterName, string cmdletName) { if (_constantClassReferences.ContainsKey(constantClassTypename)) { var reference = _constantClassReferences[constantClassTypename]; reference.AddReference(parameterName, cmdletName); } else { var reference = new ConstantClassReferences(); reference.AddReference(parameterName, cmdletName); _constantClassReferences.Add(constantClassTypename, reference); } } } /// /// Tracks the cmdlets that reference an SDK ConstantClass-derived 'enum' type /// via the same-named parameter. Cmdlets that reference the same 'enum' type /// using a different parameter name will trigger a new ConstantClassReference /// instance. /// public class ConstantClassReferences { private SortedDictionary> _references = new SortedDictionary>(); /// /// The set of parameter names that have been used to reference the /// ConstantClass-derived type. Ideally across a service their should /// be a consistent (and therefore single) name used, but we can't /// guarantee this. /// public IEnumerable ParameterNames { get { var l = new List(_references.Keys); l.Sort(); return l; } } /// /// The names of the cmdlets that reference the SDK ConstantClass-derived /// type via a parameter of name parameterName. The names are returned in /// sorted order so we generate consistently ordered file contents. /// public IEnumerable GetCmdletReferences(string parameterName) { if (!_references.ContainsKey(parameterName)) throw new ArgumentException("No reference for specified parameter name"); var refs = _references[parameterName]; var ret = new List(refs); ret.Sort(); return ret; } /// /// Adds a reference from a cmdlet via the named parameter. /// /// /// public void AddReference(string parameterName, string cmdletName) { if (_references.ContainsKey(parameterName)) { var r = _references[parameterName]; r.Add(cmdletName); } else { var r = new HashSet(); r.Add(cmdletName); _references.Add(parameterName, r); } } } }