Skip to content
This repository has been archived by the owner on Oct 21, 2023. It is now read-only.

Tag argument transformation attribute #69

Closed
indented-automation opened this issue Feb 28, 2020 · 4 comments
Closed

Tag argument transformation attribute #69

indented-automation opened this issue Feb 28, 2020 · 4 comments

Comments

@indented-automation
Copy link
Collaborator

indented-automation commented Feb 28, 2020

This attribute can be used to decorate -Tag parameters to automatically rewrite the tag using Add-VSTag.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;

public class TransformTagAttribute : ArgumentTransformationAttribute {
    private EngineIntrinsics engineIntrinsics;

    private IEnumerable<PSObject> InvokeAddVSTag(object key, object value)
    {
        var tags =  this.engineIntrinsics.InvokeCommand.InvokeScript(
            String.Format("Add-VSTag -Key '{0}' -Value '{1}'", key, value)
        );
        
        foreach (PSObject tag in tags)
        {
            yield return tag;
        }
    }

    private IEnumerable<PSObject> TransformHashtable(IDictionary inputData)
    {
        foreach (string key in inputData.Keys)
        {
            foreach (PSObject tag in this.InvokeAddVSTag(key, inputData[key]))
            {
                yield return tag;
            }
        }
    }

    private IEnumerable<PSObject> TransformPSObject(PSObject inputData)
    {
        foreach (var property in inputData.Properties)
        {
            foreach (PSObject tag in this.InvokeAddVSTag(property.Name, property.Value))
            {
                yield return tag;
            }
        }
    }

    private object TransformData(object inputData)
    {
        if (inputData is Array)
        {
            foreach (object item in inputData as object[])
            {
                return this.TransformData(item);
            }
        }
        else if (inputData is IDictionary)
        {
            return this.TransformHashtable(inputData as IDictionary);
        }
        else if (inputData is PSObject)
        {
            PSObject psObject = inputData as PSObject;
            if (psObject.TypeNames.Contains("Vaporshell.Resource.Tag"))
            {
                return inputData;
            }
            else if (psObject.TypeNames.Contains("System.Management.Automation.PSCustomObject"))
            {
                return this.TransformPSObject(psObject);
            }
        }

        return inputData;
    }

    public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
    {
        this.engineIntrinsics = engineIntrinsics;
        return this.TransformData(inputData);
    }
}

It will convert any of the following (including mix-and-match):

Test-TagBinding -Tags @{ one = 1; two = 2 }
Test-TagBinding -Tags @{ one = 1 }, @{ two = 2 }
Test-TagBinding -Tags ([PSCustomobject]@{ one = 1; two = 2 })
Test-TagBinding -Tags @(
    Add-VSTag -Key one -Value '1'
)
Test-TagBinding -Tags (Add-VSTag -Key one -Value '1')
@indented-automation
Copy link
Collaborator Author

Test-TagBinding was just this, forgot to include it somehow.

function Test-TagBinding {
    [CmdletBinding()]
    param (
        [TransformTag()]
        [object[]] $Tags
    )

    $Tags
}

scrthq added a commit that referenced this issue Mar 3, 2020
@scrthq
Copy link
Member

scrthq commented Mar 4, 2020

@indented-automation I was able to add in the additional logic, working well outside of one case when compiled:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;

namespace VaporShell.Core
{
    public class TransformTagAttribute : ArgumentTransformationAttribute
    {
        private EngineIntrinsics engineIntrinsics;

        private IEnumerable<PSObject> ConvertToTag(object key, object value)
        {
            PSObject tag = new PSObject();
            tag.Members.Add(new PSNoteProperty("Key", key));
            tag.Members.Add(new PSNoteProperty("Value", value));
            tag.TypeNames.Insert(0,"Vaporshell.Resource.Tag");

            yield return tag;
        }

        private IEnumerable<PSObject> TransformHashtable(IDictionary inputData)
        {
            if (inputData.Contains("Key") && inputData.Contains("Value"))
            {
                foreach (PSObject tag in this.ConvertToTag(inputData["Key"],inputData["Value"]))
                {
                    yield return tag;
                }
            }
            else
            {
                foreach (string key in inputData.Keys)
                {
                    foreach (PSObject tag in this.ConvertToTag(key,inputData[key]))
                    {
                        yield return tag;
                    }
                }
            }
        }

        private IEnumerable<PSObject> TransformPSObject(PSObject inputData)
        {
            var props = new List<string>();
            foreach (var property in inputData.Properties)
            {
                props.Add(property.Name);
            }
            if (props.Contains("Key") && props.Contains("Value"))
            {
                object key = null;
                object value = null;

                foreach (var property in inputData.Properties)
                {
                    if (property.Name == "Key")
                    {
                        key = property.Value;
                    }
                    else if (property.Name == "Value")
                    {
                        value = property.Value;
                    }
                }
                foreach (PSObject tag in this.ConvertToTag(key,value))
                {
                    yield return tag;
                }
            }
            else
            {
                foreach (var property in inputData.Properties)
                {
                    foreach (PSObject tag in this.ConvertToTag(property.Name,property.Value))
                    {
                        yield return tag;
                    }
                }
            }
        }

        private object TransformData(object inputData)
        {
            if (inputData.GetType().IsArray)
            {
                foreach (object item in inputData as object[])
                {
                    return this.TransformData(item);
                }
            }
            else if (inputData is IDictionary)
            {
                return this.TransformHashtable(inputData as IDictionary);
            }
            else if (inputData is PSObject)
            {
                PSObject psObject = inputData as PSObject;
                if (psObject.TypeNames.Contains("Vaporshell.Resource.Tag"))
                {
                    return inputData;
                }
                else if (psObject.TypeNames.Contains("System.Management.Automation.PSCustomObject"))
                {
                    return this.TransformPSObject(psObject);
                }
            }

            return inputData;
        }

        public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
        {
            this.engineIntrinsics = engineIntrinsics;
            return this.TransformData(inputData);
        }
    }
}

Test case:

function Test-TagBinding {
    [CmdletBinding()]
    param (
        [VaporShell.Core.TransformTag()]
        [object[]] $Tags
    )
    $Tags
}

# It doesn't seem to be catching the array type on this one, 
#     only processing the first hashtable instead
Test-TagBinding -Tags @{ one = 1 }, @{ two = 2 } 

# Rest of the test cases work fine
Test-TagBinding -Tags @{ one = 1; two = 2 }
Test-TagBinding -Tags ([PSCustomobject]@{ one = 1; two = 2 })
Test-TagBinding -Tags @(
    Add-VSTag -Key one -Value '1'
)
Test-TagBinding -Tags (Add-VSTag -Key one -Value '1')
Test-TagBinding -Tags @{Key = 'Name';Value = 'Harold'}
Test-TagBinding -Tags ([PSCustomObject]@{Key = 'Name';Value = 'Harold'})

@scrthq
Copy link
Member

scrthq commented Mar 4, 2020

Making slight progress, but now I'm just getting null values back when the item to transform is an array. I pushed up my current code if you want to take a peek @ https://github.com/scrthq/VaporShell/blob/release/2.11.0/VaporShell.Core/VaporShell.Core.cs

[CLN#1] [3111ms] E:\Git\VaporShell
[PS 7.0.0-rc.2]>> ( New-VSEC2VPC -LogicalId 'bob' -Tags @{jon = 'jon'} -CidrBlock 10.0.0.0/16 ).Props | ConvertTo-Json -Depth 10
{
  "Type": "AWS::EC2::VPC",
  "Properties": {
    "Tags": [
      {
        "Key": "jon",
        "Value": "jon"
      }
    ],
    "CidrBlock": "10.0.0.0/16"
  }
}
[CLN#2] [71ms] E:\Git\VaporShell
[PS 7.0.0-rc.2]>> ( New-VSEC2VPC -LogicalId 'bob' -Tags @{jon = 'jon'},@{bob = 'bob'},@{tim = 'tim'} -CidrBlock 10.0.0.0/16 ).Props | ConvertTo-Json -Depth 10
{
  "Type": "AWS::EC2::VPC",
  "Properties": {
    "Tags": [
      null,
      null,
      null
    ],
    "CidrBlock": "10.0.0.0/16"
  }
}

If you want to build from source, kick off the build.ps1 first with default parameters. Once the module is compiled, you can iterate CS change testing by calling the build.ps1 script like so: .\build.ps1 -Task DotNetOnly

scrthq added a commit that referenced this issue Mar 4, 2020
## 2.11.0 - 2020-03-04

* [Issue #69](#69) + [PR #70](#70) - _Thanks, [@indented-automation](https://github.com/indented-automation)!!!_
  * Started `VaporShell.Core` class library to include with the module, first class being `TransformTagAttribute` to cleanly convert `Tags` parameter input to the appropriate format if not already.
  * Added Pester tests to confirm Tag transforms are working as expected.
* [Issue #68](#68) - _Thanks, [@indented-automation](https://github.com/indented-automation) and [@austoonz](https://github.com/austoonz)!!!_
  * Surfaced errors better on AWS SDK errors so the actual error is visible.
  * Added the `FallbackCredentialFactory` to better support running in environments where credentials files are not a practical option.
* Miscellaneous
  * Updated PseudoParameter list to current spec.
  * Added newer intrinsic functions `Add-FnCidr` and `Add-FnTransform`.
@scrthq scrthq mentioned this issue Mar 4, 2020
scrthq added a commit that referenced this issue Mar 4, 2020
## 2.11.0 - 2020-03-04

* [Issue #69](#69) + [PR #70](#70) - _Thanks, [@indented-automation](https://github.com/indented-automation)!!!_
  * Started `VaporShell.Core` class library to include with the module, first class being `TransformTagAttribute` to cleanly convert `Tags` parameter input to the appropriate format if not already.
  * Added Pester tests to confirm Tag transforms are working as expected.
* [Issue #68](#68) - _Thanks, [@indented-automation](https://github.com/indented-automation) and [@austoonz](https://github.com/austoonz)!!!_
  * Surfaced errors better on AWS SDK errors so the actual error is visible.
  * Added the `FallbackCredentialFactory` to better support running in environments where credentials files are not a practical option.
* Miscellaneous
  * Updated PseudoParameter list to current spec.
  * Added newer intrinsic functions `Add-FnCidr` and `Add-FnTransform`.
@scrthq
Copy link
Member

scrthq commented Mar 4, 2020

Deployed in 2.11.0.x!

@scrthq scrthq closed this as completed Mar 4, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants