forked from StartAutomating/PSDevOps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Import-BuildStep.ps1
233 lines (215 loc) · 10.1 KB
/
Import-BuildStep.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
function Import-BuildStep
{
<#
.Synopsis
Imports Build Steps
.Description
Imports Build Steps defined in a module.
.Example
Import-BuildStep -ModuleName PSDevOps
.Link
Convert-BuildStep
.Link
Expand-BuildStep
#>
[OutputType([Nullable])]
[CmdletBinding(DefaultParameterSetName='Module')]
param(
# The name of the module containing build steps.
[Parameter(Mandatory,ParameterSetName='Module',ValueFromPipelineByPropertyName)]
[Alias('Name')]
[string]
$ModuleName,
# The source path. This path contains definitions for a given single build system.
[Parameter(Mandatory,ParameterSetName='SourcePath',ValueFromPipelineByPropertyName)]
[Alias('Fullname')]
[string]
$SourcePath,
# A list of commands to include.
[Parameter(ParameterSetName='Module',ValueFromPipelineByPropertyName)]
[string[]]
$IncludeCommand = '*',
# A list of commands to exclude
[Parameter(ParameterSetName='Module',ValueFromPipelineByPropertyName)]
[string[]]
$ExcludeCommand,
# The different build systems supported.
# Each buildsystem is the name of a subdirectory that can contain steps or other components.
[ValidateSet('ADOPipeline', 'GitHubWorkflow')]
[string[]]
$BuildSystem = @('ADOPipeline', 'GitHubWorkflow'),
# A list of valid directory aliases for a given build system.
# By default, ADOPipelines can exist within a directory named ADOPipeline, ADO, AzDO, or AzureDevOps.
# By default, GitHubWorkflows can exist within a directory named GitHubWorkflow, GitHubWorkflows, or GitHub.
[Alias('BuildSystemAliases')]
[Collections.IDictionary]
$BuildSystemAlias = $(@{
ADOPipeline = 'ADO', 'AzDO', 'AzureDevOps'
GitHubWorkflow = 'GitHub', 'GitHubWorkflows'
})
)
begin {
# In order to generically import steps for any given buildstep,
# we need two collection caches, and we need them to be fast.
if (-not $script:ComponentMetaData) {
# The ComponentMetaData maps the name of component
# to the metadata extracted from a file or function.
$script:ComponentMetaData = [Collections.Generic.Dictionary[
string,
Collections.Generic.Dictionary[string,PSObject]
]]::new([StringComparer]::OrdinalIgnoreCase)
# For usability, this should be a case-insensitive map.
}
if (-not $script:ComponentNames) {
# The Component names maps the name of each type of component (represented by a directory name)
# to the full name in the component metadata.
$script:ComponentNames = [Collections.Generic.Dictionary[
string,
Collections.Generic.Dictionary[
string,
Collections.Generic.List[string]
]
]]::new([StringComparer]::OrdinalIgnoreCase)
# For usability, this should also be a case-insensitive map.
}
}
process {
if ($PSCmdlet.ParameterSetName -eq 'Module') {
$module = # If the piped in object is a module,
if ($_ -is [Management.Automation.PSModuleInfo]) {
$_ # use it directly.
} else {
Get-Module $ModuleName | # otherwise, get the loaded copies of the module
Select-Object -First 1 # and pick the first one.
}
if (-not $module) { return } # If we could not resolve the module, return.
#region Import Module Files
$d = [IO.DirectoryInfo][IO.Path]::GetDirectoryName($Module.Path) # Get the module root
foreach ($id in $d.GetDirectories()) {
$componentTypeName = $id.Name
$resolvedBuildSystems = # Find any subdirectories that are named with a -BuildSystem
@(if ($BuildSystem -contains $id.Name) {
$id.Name
} else {
# Or named with a -BuildSystemAlias.
foreach ($kv in $BuildSystemAlias.GetEnumerator()) {
if ($kv.Value -contains $id.Name -and
$BuildSystem -contains $kv.Key) {
$kv.Key
}
}
})
if ($resolvedBuildSystems) { # If any build systems resolved,
# Import steps from that directory
foreach ($rbs in $resolvedBuildSystems) {
# for that build system.
Import-BuildStep -SourcePath $id.Fullname -BuildSystem $rbs
}
}
}
#endregion Import Module Files
#region Import Module Commands
# Next walk over the list of exported commands
:nextCmd foreach ($exCmd in $Module.ExportedCommands.Values) {
$shouldInclude = $false
# Check to see if each should be included.
foreach ($Inclusion in $IncludeCommand) {
$shouldInclude = $exCmd -like $Inclusion
if ($shouldInclude) { break }
}
if (-not $shouldInclude) { continue }
# Then check to see if each should be excluded.
foreach ($exclusion in $ExcludeCommand) {
if ($exCmd -like $exclusion) {
continue nextCmd
}
}
# Then import the command into each build system as a 'Step'
foreach ($componentTypeName in $BuildSystem) {
if (-not $script:ComponentNames.ContainsKey($componentTypeName)) {
$script:ComponentNames[$componentTypeName] =
[Collections.Generic.Dictionary[
string,
Collections.Generic.List[string]
]]::new([StringComparer]::OrdinalIgnoreCase)
$script:ComponentMetaData[$componentTypeName] =
[Collections.Generic.Dictionary[string,PSObject]]::new([StringComparer]::OrdinalIgnoreCase)
}
$ThingNames = $ComponentNames[ $componentTypeName]
$ThingData = $ComponentMetaData[$componentTypeName]
$t = 'Step'
if (-not $ThingNames.ContainsKey($t)) {
$ThingNames[$t] = [Collections.Generic.List[string]]::new()
}
$n = $exCmd.Name
if (-not $ThingNames[$t].Contains($n)) {
$ThingNames[$t].Add($n)
}
$ThingData["$($t).$($n)"] = [PSCustomObject][Ordered]@{
Name = $n
Type = $t
ScriptBlock = $exCmd.ScriptBlock
Module = $Module
}
}
}
#endregion Import Module Commands
}
elseif ($PSCmdlet.ParameterSetName -eq 'SourcePath') {
# If we've been provided a -SourcePath, start by making sure we only have one -BuildSystem.
if ($BuildSystem.Length -gt 1) {
Write-Error "Can only import from a -SourcePath for one -BuildSystem at a time."
return
}
$bs = $BuildSystem[0]
$sourceItem = Get-Item $SourcePath
if ($sourceItem -isnot [IO.DirectoryInfo]) { # If the source path isn't a directory, error out.
Write-Error "-SourcePath must be a directory."
return
}
if ($sourceItem.Name -ne $bs -and
$sourceItem.Name -notin $BuildSystemAlias[$bs]) {
# If the source path wasn't a _valid_ directory name, error out.
Write-Error (@(
"-SourcePath must match -BuildSystem '$BuildSystem'.
Must be '$BuildSystem' or '$($BuildSystemAlias[$bs] -join "','")'"
) -join ([Environment]::NewLine))
return
}
#region Import Files for a BuildSystem
if (-not $script:ComponentNames.ContainsKey($bs)) { # Create a cash of data for this buildsystem
$script:ComponentNames[$bs] =
[Collections.Generic.Dictionary[
string,
Collections.Generic.List[string]
]]::new([StringComparer]::OrdinalIgnoreCase)
$script:ComponentMetaData[$bs] =
[Collections.Generic.Dictionary[string,PSObject]]::new([StringComparer]::OrdinalIgnoreCase)
}
# Get all of the files beneath this point
$fileList = Get-ChildItem -Filter * -Recurse -LiteralPath $sourceItem.FullName
$ThingNames = $script:ComponentNames[ $bs]
$ThingData = $script:ComponentMetaData[$bs]
foreach ($f in $fileList) {
if ($f.Directory -eq $rootDir) { continue } # Skip all files more than one level down.
if ($f -is [IO.DirectoryInfo]) { continue } # Skip all directories.
$n = $f.Name.Substring(0, $f.Name.Length - $f.Extension.Length)
$t = $f.Directory.Name.TrimEnd('s') # Depluralize the directory name.
if (-not $ThingNames.ContainsKey($t)) {
$ThingNames[$t] = [Collections.Generic.List[string]]::new()
}
if (-not $ThingNames[$t].Contains($n)) {
$ThingNames[$t].Add($n)
}
# Make a collection of metadata for the thing, and store it by it's disambiguated name.
$ThingData["$($t).$($n)"] = [PSCustomObject][Ordered]@{
Name = $n
Type = $t
Extension = $f.Extension
Path = $f.FullName
}
}
#endregion Import Files for a BuildSystem
}
}
}