Runway.Yaml.psm1

<#
    Code in this file will be added to the beginning of the .psm1. For example,
    you should place any using statements here.
#>

Function Get-RwJobYaml {
    [cmdletbinding(
        DefaultParameterSetName = 'ById'
    )]
    param (
        [Parameter(
            Mandatory,
            ParameterSetName = 'ById'
        )]
        [Parameter(
            Mandatory,
            ParameterSetName = 'ById-Id'
        )]
        [Parameter(
            Mandatory,
            ParameterSetName = 'ById-Name'
        )]
        [string[]]$JobId,
        [Parameter(
            Mandatory,
            ParameterSetName = 'ByName'
        )]
        [Parameter(
            Mandatory,
            ParameterSetName = 'ByName-Id'
        )]
        [Parameter(
            Mandatory,
            ParameterSetName = 'ByName-Name'
        )]
        [string[]]$JobName,
        [Parameter(
            Mandatory,
            ParameterSetName = 'ByName-Id'
        )]
        [Parameter(
            Mandatory,
            ParameterSetName = 'ById-Id'
        )]
        [switch]$IncludeAssignedRunnersById,
        [Parameter(
            Mandatory,
            ParameterSetName = 'ByName-Name'
        )]
        [Parameter(
            Mandatory,
            ParameterSetName = 'ById-Name'
        )]
        [switch]$IncludeAssignedRunnersByName
    )
    $jobs = if ($PSCmdlet.ParameterSetName -like 'ByName*') {
        foreach ($name in $jobName) {
            Get-RwJobByName -JobName $name
        }
    } elseif ($PSCmdlet.ParameterSetName -like 'ById*') {
        foreach ($id in $JobId) {
            Get-RwJob -JobId $id
        }
    }

    # Build basic job
    $jobHt = @{}
    $jobHt['jobs'] = @{}
    foreach ($job in $jobs ) {
        $jobHt['jobs'][$job.Name] = [ordered]@{
            tags     = @($job.Tags)
            schedule = [ordered]@{
                type          = $job.Schedule.ScheduleType
                weekdays      = $job.Schedule.Weekdays
                time          = $job.Schedule.Time
                repeatMinutes = $job.Schedule.RepeatMinutes
            }
            runners  = @{
                tags = @( 'setme' )
            }
            actions  = foreach ($action in (Sort-RwJobActions -Actions $job.Actions)) {
                [ordered]@{
                    name       = $action.ActionName
                    parameters = & {
                        $ht = @{}
                        foreach ($param in $action.Settings) {
                            $ht[$param.Name] = $param.Value
                        }
                        $ht
                    }
                    connector  = @{
                        id = $action.ConnectionId
                    }
                }
            }
        }
        switch ($PSCmdlet.ParameterSetName) {
            { ($_ -like 'ByName*') } {
                $job = Get-RwJob -JobId $job.Id
            }
            { ($_ -like '*-Id') } {
                $jobHt['jobs'][$job.Name]['runners'] = @{
                    Ids = (Get-RwSetMember -SetId $job.EndpointSetId).Id
                }
            }
            { ($_ -like '*-Name') } {
                $jobHt['jobs'][$job.Name]['runners'] = @{
                    Names = (Get-RwSetMember -SetId $job.EndpointSetId).Name
                }
            }
        }
    }

    # Clean up null connectors and parameters
    $connectorCache = @{}
    foreach ($job in $jobHt['jobs'].Keys) {
        foreach ($action in $jobHt['jobs'][$job]['actions']) {
            if ($null -eq $action['connector']['id']) {
                $action.Remove('connector')
            } else {
                if ($connectorCache.Keys -notcontains $action['connector']['id']) {
                    $connectorCache[$action['connector']['id']] = Get-RwConnection -ConnectionId $action['connector']['id']
                }
                $action['connector']['name'] = $connectorCache[$action['connector']['id']].Name
                $action['connector'].Remove('id')
            }
            $toRemove = foreach ($key in $action.parameters.Keys) {
                if ($null -eq $action.parameters[$key]) {
                    $key
                }
            }
            foreach ($key in $toRemove) {
                $action.parameters.Remove($key)
            }
            if ($action.parameters.Count -eq 0) {
                $action.Remove('parameters')
            }
        }
    }

    if ($connectorCache.Keys.Count -gt 0) {
        $jobHt['connectors'] = foreach ($connector in $connectorCache.Keys) {
            $conn = Get-RwConnection -ConnectionId $connector
            [ordered]@{
                $conn.Name = [ordered]@{
                    action     = @{
                        name = $conn.ActionName
                    }
                    runner     = @{
                        name = $conn.AssignedEndpointName
                    }
                    parameters = & {
                        $ht = @{}
                        foreach ($param in $conn.Settings) {
                            $ht[$param.Name] = $param.Value
                        }
                        $ht
                    }
                }
            }
        }
    }
    
    $jobHt | ConvertTo-Yaml
}
Function Get-RwResourceFromCache {
    [cmdletbinding()]
    param (
        [Parameter(
            Mandatory
        )]
        [ValidateSet('Action','Runner')]
        [string]$ResourceType,
        [Parameter(
            Mandatory
        )]
        [string]$Name
    )
    if ($null -eq (Get-Variable -Scope Script -Name 'rwCache' -ErrorAction SilentlyContinue)) {
        $rwCache = @{}
        $rwCache['Action'] = @{}
        $rwCache['Runner'] = @{}
    }
    if ($rwCache[$ResourceType].Keys -notcontains $Name) {
        $rwCache[$ResourceType][$Name] = switch ($ResourceType) {
            'Action' { Get-RwRepository -Name $Name }
            'Runner' { Get-RwRunnerByName -AssetName $Name }
        }
    }
    $rwCache[$ResourceType][$Name]
}
Function New-RwJobSchedule {
    [cmdletbinding()]
    param (
        [int]$RepeatMinutes,
        [string]$Time,
        [string]$Type,
        [string]$WeekDays
    )
    $scheduleSplat = @{
        repeatMinutes = $RepeatMinutes
        Time = $Time
        scheduletype = if ($PSBoundParameters.Keys -contains 'Type') {
                $Type
            } else { 'RunNow' }
        weekdays = $WeekDays
    }
    New-RwJobScheduleObject @scheduleSplat
}
Function Sort-RwJobActions {
    [cmdletbinding()]
    param (
        [RunwaySdk.PowerShell.Models.ActionInstance[]]$Actions
    )
    $ht = @{}
    foreach ($action in $Actions) {
        $ht[$action.Id] = $action
    }

    for ($x = 0; $x -lt $Actions.Count; $x++) {
        if ($x -eq 0) {
            Tee-Object -InputObject ($Actions | Where-Object { $null -eq $_.PrevActionId }) -Variable prev
        } else {
            if ($prev.NextActionId) {
                Tee-Object -InputObject ($ht[$prev.NextActionId]) -Variable prev
            }
        }
    }
}
Function Sync-RwResourceYaml {
    [cmdletbinding(
        DefaultParameterSetName = 'FromString'
    )]
    param (
        [Parameter(
            ParameterSetName = 'FromString'
        )]
        [string]$Yaml,
        [Parameter(
            ParameterSetName = 'FromFile'
        )]
        [string]$PathToYaml,
        [switch]$Test
    )

    if ($PSCmdlet.ParameterSetName -eq 'FromFile') {
        $Yaml = Get-Content -Raw $PathToYaml
    }

    $currentUser = Get-RwAuthenticationCurrentUser

    Write-Verbose "Context:`n- Name: $($currentUser.name)`n- Email: $($currentUser.emailAddress)`n- Home Group: $($currentUser.homeContainerId)"

    $resources = ConvertFrom-Yaml $yaml

    # If there are any connectors
    if ($resources.connectors) {
        Write-Information "Found $($resources.connectors.count) connectors"
        Write-Information "Connectors:"

        foreach ($connector in $resources.connectors.Keys) {
            Write-Information "- $connector"

            # Leveraging a cache for the Action names
            if ($resources.connectors[$connector].Keys -contains 'action') {
                if ($resources.connectors[$connector]['action'] -contains 'id') {
                    $actionId = $resources.connectors[$connector]['action']['id']
                } else {
                    $actionId = (Get-RwResourceFromCache -ResourceType Action -Name $resources.connectors[$connector]['action']['name']).Id
                }
            }

            # Leveraging a cache for the Runner names
            if ($resources.connectors[$connector].Keys -contains 'runner') {
                if ($resources.connectors[$connector]['runner'] -contains 'id') {
                    $runnerId = $resources.connectors[$connector]['runner']['id']
                } else {
                    $runnerId = (Get-RwResourceFromCache -ResourceType Runner -Name $resources.connectors[$connector]['runner']['name']).Id
                }
            }

            $splat = @{
                Name     = $connector
                ActionId = $actionId
                RunnerId = $runnerId
                IsHidden = $false
                GroupId  = $currentUser.HomeContainerId
            }

            if ($resources.connectors[$connector].Keys -contains 'parameters') {
                $splat['settings'] = $resources.connectors[$connector]['parameters']
            }

            $conn = Get-RwConnectionByName $connector
            if ($null -ne $conn) {
                Write-Information ' - Updating existing Connector'
                if ($Test.IsPresent) {
                    Write-Information " - Would update existing Connector"
                } else {
                    Set-RwConnection @splat -ConnectionId $conn.Id -ErrorAction Stop
                }
            } else {
                Write-Information ' - Creating new connector'
                if ($Test.IsPresent) {
                    Write-Information " - Would create new Connector"
                } else {
                    New-RwConnection @splat
                }
            }

            # Assign Tags
            if ($resources.connectors[$connector].Keys -contains 'tags') {
                if ($Test.IsPresent) {
                    Write-Information " - Would add tags: $($resources.connectors[$connector]['tags'] -join ',')"
                } else {
                    # Build a set
                    $set = New-RwSet
                    # Add the job to the set
                    if ($null -eq $conn) {
                        $conn = Get-RwConnectionByName -ConnectionName $connector
                    }
                    Add-RwSetToSet -TargetSetId $set -ObjectIds $conn.Id
                    # Add the tags to the set
                    Write-Information " - Adding tags: $($resources.connectors[$connector]['tags'] -join ',')"
                    Add-RwTag -SetId $set -Tags $resources.connectors[$connector]['tags']
                }
            }
        }
    } else {
        Write-Information "No connectors found."
    }

    # If there are any jobs
    if ($resources.jobs) {
        Write-Information "Found $($resources.jobs.count) jobs"
        Write-Information 'Jobs:'

        foreach ($job in $resources.jobs.Keys) {
            Write-Information "- $job"

            # Create job if it doesn't already exist
            $existingJob = Get-RwJobByName $job

            if ($null -ne $existingJob) {
                Write-Verbose ' - Updating existing'
                $existingJob = Get-RwJob -JobId $existingJob.Id
            } else {
                Write-Verbose ' - Creating a new one'
                if ($Test.IsPresent) {
                    Write-Information " - Would create job"
                } else {
                    $newJob = New-RwJob -Name $job -IsEnabled -IsHidden:$false
                    $existingJob = Get-RwJob -JobId $newJob.JobId
                }
            }

            # Assign Schedule
            if ($resources.jobs[$job].Keys -contains 'schedule') {
                Write-Information " - Adding Schedule"

                # Create the schedule object
                $sched = $resources.jobs[$job]['schedule']
                $schedule = New-RwJobSchedule @sched

                # Set the schedule
                if ($Test.IsPresent) {
                    Write-Information " - Would set schedule to $($scheduleSplat | ConvertTo-Json -Compress)"
                } else {
                    Set-RwJobSchedule -JobId $existingJob.Id -Schedule $schedule
                }
            }

            # Assign Actions
            if ($resources.jobs[$job].Keys -contains 'actions') {
                Write-Information ' - Adding Actions'
                $x = 0
                $actions = foreach ($action in $resources.jobs[$job]['actions']) {
                    $x++
                    $actionHt = @{}
                    if ($action.Keys -contains 'id') {
                        $actionHt['RepositoryActionId'] = $action['id']
                    } else {
                        $actionHt['RepositoryActionId'] = (Get-RwResourceFromCache -ResourceType Action -Name $action['name']).Id
                    }
                    Write-Information " - $x`: '$($action['name'])'"
                    Write-Verbose "Associating '$($action['name'])' to '$($actionHt['RepositoryActionId'])'"
                    if ($action.Keys -contains 'parameters') {
                        Write-Information " - Adding parameters"
                        $actionHt['Settings'] = $action['parameters']
                    }

                    if ($action.Keys -contains 'connector') {
                        Write-Information " - Adding connector"
                        if ($action['connector'].Keys -contains 'id') {
                            $actionHt['ConnectionId'] = $action['connector']['id']
                        } elseif ($action['connector'].Keys -contains 'name') {
                            $actionHt['ConnectionId'] = (Get-RwConnectionByName -ConnectionName $action['connector']['name']).Id
                        }
                    }

                    $actionHt
                }
                if ($Test.IsPresent) {
                    Write-Information " - Would Update Actions"
                } else {
                    Set-RwJobAction -JobId $existingJob.Id -Request $actions
                }
            }

            # Assign Runners
            if ($resources.jobs[$job].Keys -contains 'runners') {
                Write-Information ' - Adding Runners'
                $newMembers = if ($resources.jobs[$job]['runners'].Keys -contains 'names') {
                    Write-Information " - Adding runners by name"
                    (Get-RwRunnerByName -AssetName $resources.jobs[$job]['runners']['names']).AssetId
                } elseif ($resources.jobs[$job]['runners'].Keys -contains 'tags') {
                    Write-Information " - Adding Runners by tags: '$($resources.jobs[$job]['runners']['tags'] -join "','")'."
                    (Get-RwEndpointByTag -Tags $resources.jobs[$job]['runners']['tags']).Id
                }

                Write-Information " - Found $($newMembers.Count) total Runners that should be assigned"

                if ($Test.IsPresent) {
                    Write-Information " - Would update membership"
                } else {
                    Sync-RwSetMembership -Members $newMembers -SetId $existingJob.EndpointSetId
                }
            }

            # Assign Tags
            if ($resources.jobs[$job].Keys -contains 'tags') {
                if ($Test.IsPresent) {
                    Write-Information " - Would add tags: $($resources.jobs[$job]['tags'] -join ',')"
                } else {
                    # Build a set
                    $set = New-RwSet
                    # Add the job to the set
                    Add-RwSetToSet -TargetSetId $set -ObjectIds $existingJob.Id
                    # Add the tags to the set
                    Write-Information " - Adding tags: $($resources.jobs[$job]['tags'] -join ',')"
                    Add-RwTag -SetId $set -Tags $resources.jobs[$job]['tags']
                }
            }
        }
    } else {
        Write-Information "No jobs found."
    }
}
Function Sync-RwSetMembership {
    [CmdletBinding()]
    param (
        [string[]]$Members,
        [string]$SetId
    )
    $existingMembers = Get-RwSetMember -SetId $SetId

    Write-Verbose "Current set membership count: $($existingMembers.Count)"

    # Finding any existing members that don't meet the filter
    $toRemove = [System.Collections.Generic.List[string]]::new()
    $toAdd = [System.Collections.Generic.List[string]]::new()
    foreach ($existingMember in $existingMembers.Items) {
        if ($newMembers.Items.Id -notcontains $existingMember.Id) {
            #Write-Verbose "Will remove $($existingMember.Id)"
            $toRemove.Add($existingMember.Id)
        }
    }

    # Find any matching members that need to be added
    foreach ($newMember in $Members) {
        if ($existingMembers.Items.Id -notcontains $newMember) {
            #Write-Verbose "Will add $($newMember)"
            $toAdd.Add($newMember)
        }
    }

    # Remove unneeded runners
    if ($toRemove.Count -gt 0) {
        Write-Verbose "Removing $($toRemove.Count) Runners from the set"
        Remove-RwSetFromSet -TargetSetId $SetId -ObjectIds $toRemove | Out-Null
    }

    # Add those runners to the job set
    if ($toAdd.Count -gt 0) {
        Write-Verbose "Adding $($toRemove.Count) Runners to the set"
        Add-RwSetToSet -TargetSetId $SetId -ObjectIds $toAdd | Out-Null
    }
}
<#
    Code in this file will be added to the end of the .psm1. For example,
    you should set variables or other environment settings here.
#>