Public/System/Add-VergeTagMember.ps1

function Add-VergeTagMember {
    <#
    .SYNOPSIS
        Assigns a tag to a resource in VergeOS.

    .DESCRIPTION
        Add-VergeTagMember assigns a tag to a resource such as a VM, network, tenant,
        or other taggable object. The tag must already exist and the resource type
        must be enabled for tagging in the tag's category.

    .PARAMETER Tag
        The tag to assign. Accepts a tag name, key, or Verge.Tag object.

    .PARAMETER VM
        A VM to tag. Accepts a VM name, key, or Verge.VM object.

    .PARAMETER Network
        A network to tag. Accepts a network name, key, or Verge.Network object.

    .PARAMETER Tenant
        A tenant to tag. Accepts a tenant name, key, or Verge.Tenant object.

    .PARAMETER ResourceType
        The type of resource when using ResourceKey parameter.
        Valid values: vms, vnets, volumes, tenants, users, groups, nodes, clusters, sites, vnet_rules

    .PARAMETER ResourceKey
        The key (ID) of the resource to tag. Must be used with ResourceType.

    .PARAMETER PassThru
        Return the created tag member object.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Add-VergeTagMember -Tag "Production" -VM "WebServer01"

        Assigns the "Production" tag to the VM named "WebServer01".

    .EXAMPLE
        Get-VergeVM -Name "Web*" | Add-VergeTagMember -Tag "Production"

        Tags all VMs starting with "Web" with the "Production" tag.

    .EXAMPLE
        Add-VergeTagMember -Tag "Production" -Network "DMZ"

        Assigns the "Production" tag to the "DMZ" network.

    .EXAMPLE
        Get-VergeTag -Name "Production" | Add-VergeTagMember -VM "WebServer01"

        Assigns a tag to a VM using pipeline input for the tag.

    .EXAMPLE
        Add-VergeTagMember -Tag "Critical" -ResourceType vms -ResourceKey 42

        Assigns a tag using the generic resource type and key parameters.

    .OUTPUTS
        None by default. Verge.TagMember when -PassThru is specified.

    .NOTES
        Use Get-VergeTagMember to list existing tag assignments.
        Use Remove-VergeTagMember to remove tag assignments.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'VM')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [object]$Tag,

        [Parameter(Mandatory, ParameterSetName = 'VM', ValueFromPipeline)]
        [object]$VM,

        [Parameter(Mandatory, ParameterSetName = 'Network')]
        [Alias('VNet')]
        [object]$Network,

        [Parameter(Mandatory, ParameterSetName = 'Tenant')]
        [object]$Tenant,

        [Parameter(Mandatory, ParameterSetName = 'Generic')]
        [ValidateSet('vms', 'vnets', 'volumes', 'tenants', 'users', 'groups', 'nodes', 'clusters', 'sites', 'vnet_rules', 'vmware_containers', 'tenant_nodes')]
        [string]$ResourceType,

        [Parameter(Mandatory, ParameterSetName = 'Generic')]
        [int]$ResourceKey,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Resolve tag once in begin block
        $resolvedTagKey = $null
        $resolvedTagName = $null

        # Handle Verge.Tag object
        if ($Tag.PSObject.TypeNames -contains 'Verge.Tag') {
            $resolvedTagKey = $Tag.Key
            $resolvedTagName = $Tag.Name
        }
        # Handle integer key
        elseif ($Tag -is [int]) {
            $resolvedTagKey = $Tag
            $foundTag = Get-VergeTag -Key $resolvedTagKey -Server $Server
            if ($foundTag) {
                $resolvedTagName = $foundTag.Name
            }
        }
        # Handle string (name or key)
        elseif ($Tag -is [string]) {
            if ($Tag -match '^\d+$') {
                $resolvedTagKey = [int]$Tag
                $foundTag = Get-VergeTag -Key $resolvedTagKey -Server $Server
                if ($foundTag) {
                    $resolvedTagName = $foundTag.Name
                }
            }
            else {
                # Look up tag by name
                $foundTag = Get-VergeTag -Name $Tag -Server $Server | Select-Object -First 1
                if ($foundTag) {
                    $resolvedTagKey = $foundTag.Key
                    $resolvedTagName = $foundTag.Name
                }
            }
        }

        if (-not $resolvedTagKey) {
            throw "Tag '$Tag' not found. Use Get-VergeTag to list available tags."
        }
    }

    process {
        # Resolve the resource reference based on parameter set
        $memberRef = $null
        $memberName = $null
        $memberType = $null

        switch ($PSCmdlet.ParameterSetName) {
            'VM' {
                $memberType = 'vms'
                if ($VM.PSObject.TypeNames -contains 'Verge.VM') {
                    $memberRef = "vms/$($VM.Key)"
                    $memberName = $VM.Name
                }
                elseif ($VM -is [int]) {
                    $memberRef = "vms/$VM"
                    $existingVM = Get-VergeVM -Key $VM -Server $Server -ErrorAction SilentlyContinue
                    $memberName = if ($existingVM) { $existingVM.Name } else { "VM $VM" }
                }
                elseif ($VM -is [string]) {
                    $existingVM = Get-VergeVM -Name $VM -Server $Server -ErrorAction SilentlyContinue | Select-Object -First 1
                    if ($existingVM) {
                        $memberRef = "vms/$($existingVM.Key)"
                        $memberName = $existingVM.Name
                    }
                    else {
                        Write-Error -Message "VM not found: $VM" -ErrorId 'VMNotFound' -Category ObjectNotFound
                        return
                    }
                }
            }
            'Network' {
                $memberType = 'vnets'
                if ($Network.PSObject.TypeNames -contains 'Verge.Network') {
                    $memberRef = "vnets/$($Network.Key)"
                    $memberName = $Network.Name
                }
                elseif ($Network -is [int]) {
                    $memberRef = "vnets/$Network"
                    $existingNetwork = Get-VergeNetwork -Key $Network -Server $Server -ErrorAction SilentlyContinue
                    $memberName = if ($existingNetwork) { $existingNetwork.Name } else { "Network $Network" }
                }
                elseif ($Network -is [string]) {
                    $existingNetwork = Get-VergeNetwork -Name $Network -Server $Server -ErrorAction SilentlyContinue | Select-Object -First 1
                    if ($existingNetwork) {
                        $memberRef = "vnets/$($existingNetwork.Key)"
                        $memberName = $existingNetwork.Name
                    }
                    else {
                        Write-Error -Message "Network not found: $Network" -ErrorId 'NetworkNotFound' -Category ObjectNotFound
                        return
                    }
                }
            }
            'Tenant' {
                $memberType = 'tenants'
                if ($Tenant.PSObject.TypeNames -contains 'Verge.Tenant') {
                    $memberRef = "tenants/$($Tenant.Key)"
                    $memberName = $Tenant.Name
                }
                elseif ($Tenant -is [int]) {
                    $memberRef = "tenants/$Tenant"
                    $existingTenant = Get-VergeTenant -Key $Tenant -Server $Server -ErrorAction SilentlyContinue
                    $memberName = if ($existingTenant) { $existingTenant.Name } else { "Tenant $Tenant" }
                }
                elseif ($Tenant -is [string]) {
                    $existingTenant = Get-VergeTenant -Name $Tenant -Server $Server -ErrorAction SilentlyContinue | Select-Object -First 1
                    if ($existingTenant) {
                        $memberRef = "tenants/$($existingTenant.Key)"
                        $memberName = $existingTenant.Name
                    }
                    else {
                        Write-Error -Message "Tenant not found: $Tenant" -ErrorId 'TenantNotFound' -Category ObjectNotFound
                        return
                    }
                }
            }
            'Generic' {
                $memberType = $ResourceType
                $memberRef = "$ResourceType/$ResourceKey"
                $memberName = "$ResourceType/$ResourceKey"
            }
        }

        if (-not $memberRef) {
            Write-Error -Message "Could not resolve resource reference" -ErrorId 'ResourceNotResolved' -Category InvalidArgument
            return
        }

        # Build request body
        $body = @{
            tag    = $resolvedTagKey
            member = $memberRef
        }

        if ($PSCmdlet.ShouldProcess("$memberName", "Add Tag '$resolvedTagName'")) {
            try {
                Write-Verbose "Adding tag '$resolvedTagName' to $memberType '$memberName'"
                $response = Invoke-VergeAPI -Method POST -Endpoint 'tag_members' -Body $body -Connection $Server

                $membershipKey = $response.'$key'
                Write-Verbose "Tag '$resolvedTagName' assigned to '$memberName' (Membership Key: $membershipKey)"

                if ($PassThru -and $membershipKey) {
                    # Return the tag member record
                    [PSCustomObject]@{
                        PSTypeName   = 'Verge.TagMember'
                        Key          = [int]$membershipKey
                        TagKey       = $resolvedTagKey
                        TagName      = $resolvedTagName
                        ResourceType = $memberType
                        ResourceKey  = if ($ResourceKey) { $ResourceKey } else { [int]($memberRef -split '/')[-1] }
                        ResourceRef  = $memberRef
                    }
                }
            }
            catch {
                $errorMessage = $_.Exception.Message
                if ($errorMessage -match 'already' -or $errorMessage -match 'unique') {
                    Write-Warning "Tag '$resolvedTagName' is already assigned to '$memberName'"
                }
                else {
                    throw "Failed to add tag to resource: $errorMessage"
                }
            }
        }
    }
}