Многие из вас наверняка работают с разнообразными инфраструктурами, используя REST API. А поскольку все более широкие слои населения для автоматизации рутинных задач осваивают PowerShell, то почему бы и не начать применять его для работы с REST API?
Сегодня вашему вниманию предлагается перевод статьи Адама Бертрама, большого поклонника автоматизации, автора сайта adamtheautomator.com, в которой он показывает на примере, как это можно сделать.
Итак, добро пожаловать под кат.
В качестве примера API у нас будет выступать swagger petstore API.
Начну, как и положено в приличных руководствах, с требований к системе. Данный пример создавался с Windows PowerShell v5.1 — проверьте, что у вас он есть. Возможно, что пример будет работать также на v6 и выше, возможно, что на Mac или на Linux ОС — но это не точно (не тестировалось).
До того, как начать писать собственно скрипт, нужно подготовить файловую структуру для нашего модуля. Тут, конечно, каждый волен делать что ему нравится, а мы для простоты создадим простейшую структуру из двух файлов и одной папки:
Создаем папку PetStore в каталоге Source на диске C:
Затем создадим файл манифеста для нашего модуля — это файл .psd1 с метаданными, где вы сможете прописать все требования к модулю и всю информацию о нём. Вообще-то никто вам не запретит создать такой файл вручную, но можно сделать это и с помощью командлета New-ModuleManifest. Само собой, нужно будет указать необходимые вам параметры.
Создаем файл манифеста под названием Petstore.psd1 в нашей новой папке на диске C:
Файл собственно модуля с расширением .psm1 как раз и будет содержать все нужные нам функции. В любом PowerShell-редакторе создадим новый файл и положим его на диск C.
Строго говоря, это необязательная операция, но будет хорошим тоном в файле .psm1 прописать основной идентификатор ресурса (base URI) для API-эндпойнтов как переменную — это многое упростит, в том числе и в будущем (переход от версии к версии). Ну а в качестве формата данных будем использовать JSON.
Получится вот так:
Затем мы сможем использовать эту переменную в любой функции с вызовом
Invoke-RestMethod:
Для начала создадим функцию, которая будет получать от эндпойнта https://petstore.swagger.io/v2/pet/ данные о питомцах. Назовем ее Get-PetstorePet:
Мы определили параметр PetId так, чтобы он соответствовал определенному параметру REST API для этого эндпойнта.
Примечание: Полный перечень параметров для данного примера эндпойнта можно найти тут.
Используя ValueFromPipeline и ValueFromPipelineByPropertyName в нашей функции, можно передавать ей параметры из пайплайна:
Для этого выполняем команду Import-Module:
После чего можно будет запускать выполнение нашей функции Get-PetstorePet:
Как разъясняется в документации для API, чтобы добавить нового питомца, нужно написать тело запроса в формате JSON. Ну и логично, что для добавления нового питомца мы будем использовать функцию Add-PetstorePet.
Поскольку PowerShell умеет в объекты, мы не будем валить всё в кучу, а создадим хэш-таблицу, которую затем преобразуем в JSON:
Это куда проще и короче, чем создавать всеобъемлющий JSON. На основе нашей хэш-таблички определим параметры, которые понадобятся нам для команды:
Поскольку блок Begin ровно такой же, как и у предыдущей команды, мы можем его переиспользовать, слегка изменив URI (т.к. параметр PetID тут уже не нужен):
Затем определим блок Process, который создаст post-запрос, используя формат JSON, и отправит его к REST API-эндпойнту, задействуя Invoke-RestMethod.
Заметьте, что мы удалили у $BodyObject свойства со значением null — ибо некоторые REST API не любят, когда им пытаются скормить такие значения.
Наша функция добавления питомца будет выглядеть в итоге так:
Снова импортируем модуль и запускаем нашу функцию:
Теперь мы освоились с функциями и можем на основе этого подхода создавать функции для других методов, работающих с REST API.
Например, на базе функции Add-PetstorePet можно создать функцию удаления Remove-PetstorePet — для этого надо дать ей соответствующее название и заменить метод, который вызывается командой Invoke-RestMethod, на метод
Поскольку параметры были настроены на прием pipeline input, вы можете спокойно организовать пайп питомцев в функцию Remove-PetstorePet:
Как вы наверняка заметили, при создании модуля PowerShell для REST API многое завязано на повторное использование. Поскольку многие REST API весьма схожи с swagger API, то вполне можно переиспользовать модуль для работы с разными API. Поддержка же пайплайна еще более упрощает работу.
Ну а если у вас тоже есть чем поделиться из своей практики автоматизации, добро пожаловать в комментарии.
Сегодня вашему вниманию предлагается перевод статьи Адама Бертрама, большого поклонника автоматизации, автора сайта adamtheautomator.com, в которой он показывает на примере, как это можно сделать.
Итак, добро пожаловать под кат.
В качестве примера API у нас будет выступать swagger petstore API.
Начну, как и положено в приличных руководствах, с требований к системе. Данный пример создавался с Windows PowerShell v5.1 — проверьте, что у вас он есть. Возможно, что пример будет работать также на v6 и выше, возможно, что на Mac или на Linux ОС — но это не точно (не тестировалось).
Готовим папку и файлы для нового модуля
До того, как начать писать собственно скрипт, нужно подготовить файловую структуру для нашего модуля. Тут, конечно, каждый волен делать что ему нравится, а мы для простоты создадим простейшую структуру из двух файлов и одной папки:
- В файле манифеста PowerShell (.psd1) будет храниться информация о новом модуле.
- В файле собственно модуля PowerShell (.psm1) будут храниться все наши командлеты.
- Ну и папка, в которой будут лежать оба эти файла.
Создаем папку PetStore в каталоге Source на диске C:
New-Item -Type Directory -Path C:\Source\PetStore
Затем создадим файл манифеста для нашего модуля — это файл .psd1 с метаданными, где вы сможете прописать все требования к модулю и всю информацию о нём. Вообще-то никто вам не запретит создать такой файл вручную, но можно сделать это и с помощью командлета New-ModuleManifest. Само собой, нужно будет указать необходимые вам параметры.
Создаем файл манифеста под названием Petstore.psd1 в нашей новой папке на диске C:
# Use splatting technique to make it more readable
# Не забывайте про комментарии к коду для пущей читабельности
$ModuleManifestParams = @{
Path = "C:\Source\PetStore\PetStore.psd1"
# Notice that the psd1 file has the same name as the folder it resides in
# Файл манифеста имеет такое же имя, что и папка
Guid = [GUID]::NewGuid().Guid
# A unique GUID for the module for identification
# GUID нового модуля
Author = "Your Name Here"
# Optional - кто автор (необязательно)
CompanyName = "Company Name here"
# Optional - компания (необязательно)
ModuleVersion = "0.0.1"
# Semantic versioning as recommended best practice for PowerShell modules
# Проставим номер версии модуля, как рекомендуется в лучших домах (семантическое версионирование, т.е. по смыслу)
Description = "A PowerShell module to interact with the HttpBin API"
# A short description of what the module does
# Кратко опишем, для чего нужен модуль
}
# Run New-ModuleManifest with the splatted parameters
# А теперь запустим командлет с указанными параметрами для создания манифеста
New-ModuleManifest @ModuleManifestParams
Файл собственно модуля с расширением .psm1 как раз и будет содержать все нужные нам функции. В любом PowerShell-редакторе создадим новый файл и положим его на диск C.
Прописываем URI для API-эндпойнтов
Строго говоря, это необязательная операция, но будет хорошим тоном в файле .psm1 прописать основной идентификатор ресурса (base URI) для API-эндпойнтов как переменную — это многое упростит, в том числе и в будущем (переход от версии к версии). Ну а в качестве формата данных будем использовать JSON.
Получится вот так:
$script:PetStoreBaseUri = "https://petstore.swagger.io/v2"
$script:PetstoreInvokeParams = @{
ContentType = "application/json"
}
Затем мы сможем использовать эту переменную в любой функции с вызовом
Invoke-RestMethod:
PS51> Invoke-RestMethod -Uri "$script:PetStoreBaseUri/pet/<petid>" @script:PetstoreInvokeParams
Создаем первую функцию
Для начала создадим функцию, которая будет получать от эндпойнта https://petstore.swagger.io/v2/pet/ данные о питомцах. Назовем ее Get-PetstorePet:
Function Get-PetstorePet {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[int64]$PetID
)
Begin {
# Using the module-scoped API endpoint URI to create a function-specific URL to query
# Используем наш URI, заданный для модуля, чтобы создать для данной функции URL, куда будут идти запросы
$Uri = "$script:PetStoreBaseUri/pet/{petId}"
}
Process {
# Create a temporary variable from the Uri variable
# Создаем временную переменную из нашей переменной URI
contain int the PetId parameter
$TempUri = $Uri -replace "\{petId\}", $PetId
# Call the API endpoint using the $TempUri
# Используем эту временную переменную для обращения к API-эндпойнту
Invoke-RestMethod -Uri $TempUri -Method Get @script:PetStoreInvokeParams
}
}
Мы определили параметр PetId так, чтобы он соответствовал определенному параметру REST API для этого эндпойнта.
Примечание: Полный перечень параметров для данного примера эндпойнта можно найти тут.
Используя ValueFromPipeline и ValueFromPipelineByPropertyName в нашей функции, можно передавать ей параметры из пайплайна:
# Define pet id's to fetch
# Указываем идентификаторы питомцев, которые нам нужны
$PetIds = @(100,101,102,103)
$PetIds | Get-PetstorePet
Импортируем модуль
Для этого выполняем команду Import-Module:
PS51> Import-Module C:\Source\Petstore
После чего можно будет запускать выполнение нашей функции Get-PetstorePet:
PS51> Get-PetstorePet -PetId 123
Добавляем новую запись
Как разъясняется в документации для API, чтобы добавить нового питомца, нужно написать тело запроса в формате JSON. Ну и логично, что для добавления нового питомца мы будем использовать функцию Add-PetstorePet.
Поскольку PowerShell умеет в объекты, мы не будем валить всё в кучу, а создадим хэш-таблицу, которую затем преобразуем в JSON:
$BodyJson = @{
id = $PetId
category = @{
id = $CategoryId
name = $CategoryName
}
name = $PetName
photoUrls = $PhotoUrls
tags = $Tags
status = $Status
} | ConvertTo-Json
Это куда проще и короче, чем создавать всеобъемлющий JSON. На основе нашей хэш-таблички определим параметры, которые понадобятся нам для команды:
param(
# Id of the pet that you'll create
# ID питомца, которого будем создавать
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[Alias("Id")]
# This is an alias so that we can use it with pipeline properly by piping output from other Functions in this module
# Это алиас, который мы сможем использовать в пайплайне, забирая то, что получается на выходе у всяких разных функций в нашем модуле
[int64]$PetId,
# Category ID of the pet to create
# ID категории, к которой будет относиться новый питомец
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[int64]$CategoryId,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$CategoryName,
# Name of the pet
# Имя питомца
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$PetName,
# Urls to photos of the pet
# URLs для фото питомца
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string[]]$PhotoUrls,
# Objects with tags containing Id and Name properties
# Объекты с тэгами, содержащими ID и Name
[Parameter(ValueFromPipelineByPropertyName)]
[PSCustomObject[]]$Tags,
# Status of the pet "Available","Taken" etc.
# Статус питомца (Доступен, Пристроен и пр.)
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$Status
)
Поскольку блок Begin ровно такой же, как и у предыдущей команды, мы можем его переиспользовать, слегка изменив URI (т.к. параметр PetID тут уже не нужен):
Begin {
$Uri = "$script:PetStoreBaseUri/pet"
}
Затем определим блок Process, который создаст post-запрос, используя формат JSON, и отправит его к REST API-эндпойнту, задействуя Invoke-RestMethod.
Process {
# Create the JSON that we'll post to the endpoint
# Создаем JSON для отправки post-запроса на эндпойнт
$BodyObject = @{
id = $PetId
category = $Category
name = $PetName
photoUrls = $PhotoUrls
tags = $Tags
status = $Status
}
# This will remove properties with a null value since it may mess around with some API's
# Удаляем свойства, у которых значение равно null, чтобы не мешали
$BodyObject.psobject.properties | ? {$_.Value -eq $Null} | Foreach {
$BodyObject.psobject.properties.Remove($_.Name)
}
$BodyJson = $BodyObject | ConvertTo-Json
# Call the API endpoint using the $TempUri
# Обращаемся к эндпойнту, используя $TempUri
Invoke-RestMethod -Uri $Script:Uri -Method POST -Body $BodyJson @PetStoreInvokeParams
}
Заметьте, что мы удалили у $BodyObject свойства со значением null — ибо некоторые REST API не любят, когда им пытаются скормить такие значения.
Наша функция добавления питомца будет выглядеть в итоге так:
Function Add-PetstorePet {
[cmdletbinding()]
param(
# Id of the pet that you'll create
# ID питомца, которого будем создавать
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[Alias("Id")]
# This is an alias so that we can use it with pipeline properly by piping output from other Functions in this module
# Это алиас, который мы сможем использовать в пайплайне, забирая то, что получается на выходе у всяких разных функций в нашем модуле
[int64]$PetId,
# hash containing Id and name of category
# Имя и ID категории, к которой будет относиться новый питомец, берем из хэш-таблички
[Parameter(ValueFromPipelineByPropertyName)]
[hashtable]$Category,
# Name of the pet
# Имя питомца
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$PetName,
# Urls to photos of the pet
# URLs для фото питомца
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string[]]$PhotoUrls,
# Objects with tags containing Id and Name properties
# Объекты с тэгами, содержащими ID и Name
[Parameter(ValueFromPipelineByPropertyName)]
[PSCustomObject[]]$Tags,
# Status of the pet "Available","Taken" etc.
# Статус питомца (Доступен, Пристроен и пр.)
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Status
)
Begin {
$Uri = "$script:PetStoreBaseUri/pet"
}
Process {
# Create the JSON that we'll post to the endpoint
# Создаем JSON для отправки post-запроса на эндпойнт
$BodyObject = @{
id = $PetId
category = $Category
name = $PetName
photoUrls = $PhotoUrls
tags = $Tags
status = $Status
}
$BodyObject.psobject.properties | ? {$_.Value -eq $Null} | Foreach {
$BodyObject.psobject.properties.Remove($_.Name)
}
$BodyJson = $BodyObject | ConvertTo-Json
# Call the API endpoint using the $TempUri
# Обращаемся к эндпойнту, используя $TempUri
Invoke-RestMethod -Uri $Uri -Method POST -Body $BodyJson @script:PetStoreInvokeParams
}
}
Снова импортируем модуль и запускаем нашу функцию:
PS51> Import-Module C:\Source\Petstore -Force
PS51> Add-PetstorePet -PetName Fido -PetId 1 -PhotoUrls "http://somesite.com/picture.jpg"
PS51> Get-PetstorePet -PetId 1
Пишем новые функции
Теперь мы освоились с функциями и можем на основе этого подхода создавать функции для других методов, работающих с REST API.
Например, на базе функции Add-PetstorePet можно создать функцию удаления Remove-PetstorePet — для этого надо дать ей соответствующее название и заменить метод, который вызывается командой Invoke-RestMethod, на метод
DELETE
, как показано ниже:Function Remove-PetstorePet {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias("Id")]
[int64]$PetId
)
Begin {
$Uri = "$script:PetStoreBaseUri/pet/{petId}"
}
Process {
# Create a temporary variable from the Uri variable containing the PetId parameter
# Создаем временную переменную из нашей переменной URI
$TempUri = $Uri -replace "\{petId\}", $PetId
# Call the API endpoint using the $TempUri
# Обращаемся к эндпойнту, используя $TempUri
Invoke-RestMethod -Uri $TempUri -Method DELETE @script:PetStoreInvokeParams
# NOTICE how we replaced GET with DELETE
# Обратите внимание, что мы заменили тут GET на DELETE
}
}
Поскольку параметры были настроены на прием pipeline input, вы можете спокойно организовать пайп питомцев в функцию Remove-PetstorePet:
PS51> Get-PetstorePet -PetId 1 | Remove-PetstorePet
В заключение
Как вы наверняка заметили, при создании модуля PowerShell для REST API многое завязано на повторное использование. Поскольку многие REST API весьма схожи с swagger API, то вполне можно переиспользовать модуль для работы с разными API. Поддержка же пайплайна еще более упрощает работу.
Ну а если у вас тоже есть чем поделиться из своей практики автоматизации, добро пожаловать в комментарии.
freiman
Нет, это, конечно, можно, и даже прикольно (почитать про такой способ).
Но зачем? Кто и при каких условиях использует такой подход, и чем он лучше использования любого языка программирования?
somurzakov
повершелл работает из коробки везде в виндовс, можно быстро автоматизировать то, для чего даже студию лень открывать.
плюс повершелл работает в SQL Agentе на сервере и можно обвязывать многие вещина него, аналог GitHub actions, в микрософт экосистеме.
freiman
Тестирование REST API к «быстро автоматизировать» не относится. Мы тут говорим не про Powershell в общем и его возможности, а именно в контексте тестирования апишек.
somurzakov
не видел слово тестирование нигде в статье, мне кажется речь шла об использовании в целом
freiman
Возможно, это у меня «профессиональная деформация»: «rest api» + «автоматизация»… :)