Download Azure Icons #335
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Download Azure Icons | |
on: | |
schedule: | |
- cron: '12 3 * * *' | |
workflow_dispatch: | |
jobs: | |
download: | |
runs-on: ubuntu-latest | |
name: Download | |
outputs: | |
confighash: ${{ steps.src.outputs.confighash }} | |
pageversion: ${{ steps.src.outputs.pageversion }} | |
steps: | |
- id: src | |
name: Download sources | |
shell: pwsh | |
run: | | |
# Azure Portal config | |
$portalconfig=if((irm "https://portal.azure.com/") -match 'MsPortalImpl\.redirect\(([^)]+)\)'){$matches[1]|convertfrom-json}else{throw} | |
$portalconfighash=$portalconfig.portalServerConfig.portalQuery.configHash | |
"confighash=$portalconfighash" >> $env:GITHUB_OUTPUT | |
$portalconfigpageversion=$portalconfig.portalServerConfig.portalQuery.pageVersion | |
"pageversion=$portalconfigpageversion" >> $env:GITHUB_OUTPUT | |
"Downloading from Azure Portal version $portalconfigpageversion with config hash $portalconfighash" >> $env:GITHUB_STEP_SUMMARY | |
# Requires config | |
$portalrequiresfile=if((irm "https://portal.azure.com/?configHash=$portalconfighash&pageVersion=$portalconfigpageversion") -match '"/Content/PortalRequireConfig/([^"]+)"'){$matches[1]}else{throw} | |
$portalrequires=if((irm "https://portal.azure.com/Content/PortalRequireConfig/$portalrequiresfile") -match 'MsPortalImpl\.setRequireConfig\(([^)]+)\)'){$matches[1]|convertfrom-json}else{throw} | |
# Icon Styles CSS | |
$portalcssfile=($portalrequires.requireConfig.dependencyTree.psobject.properties|?{$_.value.psobject.properties.name -contains '_generated/Less/MsPortalImpl/Base/Base.Images.css'}).name | |
$iconstyles=if((irm "https://portal.azure.com/$($portalcssfile).js") -match 'define\("_generated/Less/MsPortalImpl/Base/Base\.Images\.css".*return{css:"([^"]+)"'){$matches[1]}else{throw} | |
$iconstyles|out-file icon-styles.css -nonewline | |
# Image Library JSON | |
$imagelibraryfile=($portalrequires.requireConfig.dependencyTree.psobject.properties|?{$_.value.psobject.properties.name -contains 'MsPortalImpl/Base/Base.ImageLibrary'}).name | |
$imagelibraryjs=irm "https://portal.azure.com/$($imagelibraryfile).js" | |
$imagelibrary=([regex]'{type:(\d+),data:"(<svg[^"]+</svg>)"}').matches($imagelibraryjs)|select @{n='type';e={[int]($_.groups[1].value)}},@{n='svg';e={$_.groups[2].value}} | |
$svglibrary=$portalrequires.requireConfig.dependencyTree.psobject.properties|?{$_.value.psobject.properties.name -like '*.svg'}|select -exp name|sort-object -unique|%{([regex]'{type:(\d+),data:"(<svg[^"]+</svg>)"}').matches((irm "https://portal.azure.com/$($_).js"))|select @{n='type';e={[int]($_.groups[1].value)}},@{n='svg';e={$_.groups[2].value}}} | |
$iconlibrary=$imagelibrary+$svglibrary | |
$iconlibrary|convertto-json -depth 100|out-file icon-library.json | |
"Got total of $($iconlibrary.count) library icons" >> $env:GITHUB_STEP_SUMMARY | |
# Icon Assets JSON | |
$manifestindex=$portalconfig.portalServerConfig.supportedLanguages.IndexOf('en') | |
$manifesthash=$portalconfig.portalServerConfig.environment.extensionsManifestHash.assetTypes[$manifestindex][0] | |
$iconassets=(irm "https://portal.azure.com/Content/ExtensionManifest/$manifesthash.json").manifest | |
$iconassets|convertto-json -depth 100|out-file icon-assets.json | |
"Got total of $($iconassets.psobject.properties.value.count) manifests and $($iconassets.psobject.properties.value.assettypes.count) assets" >> $env:GITHUB_STEP_SUMMARY | |
- id: upload-src | |
name: Save sources | |
uses: actions/upload-artifact@v4 | |
with: | |
name: src | |
compression-level: 9 | |
path: | | |
icon-styles.css | |
icon-library.json | |
icon-assets.json | |
- name: Convert sources to icon data | |
shell: pwsh | |
run: | | |
$iconstyles=gc icon-styles.css -raw | |
$iconlibrary=gc icon-library.json|convertfrom-json | |
$iconassets=gc icon-assets.json|convertfrom-json | |
$iconlibraryhashtable=$iconlibrary|%{$ht=@{}}{$ht[[int]$_.type]=$_.svg}{$ht} | |
$icondata=$iconassets.psobject.properties|?{$_.value.assetTypes}|%{ | |
$m=$_.name | |
$_.value.assetTypes|?{$_.icon.length -gt 0}|%{ | |
$firsticon=$null | |
$firsticon=$_.icon[0] | |
$firsticontype=[int]$firsticon.type | |
if($firsticon.data -like '<svg*'){ | |
$svg=$firsticon.data | |
}elseif($firsticontype -gt 1 -and $iconlibraryhashtable.contains($firsticontype)){ | |
$svg=$iconlibraryhashtable[$firsticontype] | |
}else{ | |
$svg='<svg viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><text x="0" y="25" class="small">'+$firsticontype+'</text></svg>' | |
} | |
$svg=$svg -replace "xmlns:svg='http://www.w3.org/2000/svg'","xmlns='http://www.w3.org/2000/svg'" | |
if($svg -match '^<svg([^>]*)>'){if(-not ($matches[1] -match "xmlns='http://www\.w3\.org/2000/svg'")){$svg=$svg -replace "<svg$($matches[1])>","<svg$($matches[1]) xmlns='http://www.w3.org/2000/svg'>"}} | |
$svg=$svg -replace '</svg>',"<style>$iconstyles</style></svg>" | |
$svg=$svg -replace '<title></title>',"<title>$([web.httputility]::htmlencode(($_.singularDisplayName -replace "`n",'')))</title><desc>$([web.httputility]::htmlencode(($_.description -replace "`n",' ')))</desc>" | |
[regex]::matches($svg,'url\(#(?<guid>[0-9a-fA-F-]{36})\)')|%{$i=0}{$svg=$svg -replace $_.groups['guid'].value,"definition-$($i++;$i)"} | |
[pscustomobject]@{ | |
name="$($m)/$($_.name)" | |
svg=$svg | |
singularDisplayName=$_.singularDisplayName -replace "`n",'' | |
pluralDisplayName=$_.pluralDisplayName -replace "`n",'' | |
resourceType=$_.resourceType.resourceTypeName -replace "`n",'' | |
keywords=$_.keywords -replace "`n",' ' | |
description=$_.description -replace "`n",' ' | |
} | |
} | |
} | |
"Resolved $($icondata.count) icons" >> $env:GITHUB_STEP_SUMMARY | |
$icondata|convertto-json -depth 100|out-file icon-data.json | |
- id: upload-data | |
name: Upload icon data | |
uses: actions/upload-artifact@v4 | |
with: | |
name: data | |
compression-level: 9 | |
path: icon-data.json | |
- name: Save SVG icons | |
shell: pwsh | |
run: | | |
$icondata=gc icon-data.json|convertfrom-json | |
new-item -itemtype directory -name svg|out-null | |
$iconstat=$icondata|%{$_.svg|new-item -path "svg/$($_.name).svg" -force}|measure-object -property length -sum | |
"Created $($iconstat.count) svg files with total size $([math]::round($iconstat.sum/1024/1024, 1))MB" | |
- name: Compose HTML | |
shell: pwsh | |
run: | | |
$icondata=gc icon-data.json|convertfrom-json | |
$html=$icondata|% ` | |
{"<html><head><meta name=`"viewport`" content=`"width=device-width, initial-scale=1`"><title>Azure Icons</title><style>body{margin:0}table,th,td{border:1px solid #CCCCCC;border-collapse:collapse;padding:3px}tbody{word-break:break-all;}thead,tfoot{position:sticky;}thead{inset-block-start:0;background-color:#CCCCCC}tfoot{inset-block-end:0;background-color:#CCCCCC}a:link,a:visited,a:hover{color:black}a:active{color:blue}</style><script>function download(uri, name){const a=document.createElement(`"a`");a.href=uri;a.download=name+`".svg`";a.click();}</script></head><body><table><thead><tr><th scope=`"col`" style=`"width: 10%`">Click to download</th><th scope=`"col`" style=`"width: 20%`">Name</th><th scope=`"col`" style=`"width: 20%`">Type</th><th scope=`"col`" style=`"width: 20%`">Keywords</th><th scope=`"col`" style=`"width: 30%`">Description</th></tr></thead><tbody>"} ` | |
{"<tr title=`"$($_.name)`"><td><img loading=`"lazy`" decoding=`"async`" src=`"svg/$($_.name).svg`" alt=`"$($_.singularDisplayName)`" title=`"Click to download`" onclick=`"download(this.src, this.alt)`" /></td><td>$($_.singularDisplayName)</td><td>$($_.resourceType)</td><td>$($_.keywords)</td><td>$($_.description)</td></tr>"} ` | |
{"</tbody><tfoot><tr><td colspan=`"5`">Extracted from Azure Portal version ${{ steps.src.outputs.pageversion }} on $(get-date -f 'yyyy-MM-dd'). Icons <a target=`"_blank`" href=`"https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/permissions`">used with permission from Microsoft</a>. Source on <a target=`"_blank`" href=`"https://github.com/maskati/azure-icons`">GitHub</a>.</td></tr></tfoot></table></body></html>"}|out-string | |
$html|out-file index.html | |
"Created HTML of $([math]::round((gi index.html).length/1024/1024, 1))MB" >> $env:GITHUB_STEP_SUMMARY | |
- id: upload-azure-icons | |
name: Upload Azure Icons | |
uses: actions/upload-artifact@v4 | |
with: | |
name: azure-icons | |
compression-level: 9 | |
path: | | |
svg/ | |
index.html | |
# https://github.com/jgraph/drawio-libs?tab=readme-ov-file#library-file-format | |
- name: Compose drawio library | |
shell: pwsh | |
run: | | |
$icondata=gc icon-data.json|convertfrom-json | |
$mxicons=$icondata|%{ | |
[ordered]@{ | |
h=50;w=50; | |
aspect='fixed'; | |
title=(@($_.singularDisplayName, $_.name, $_.resourcetype)|?{-not [string]::isnullorwhitespace($_)}) -join ' | '; | |
data="data:image/svg+xml;base64,$([convert]::tobase64string([text.encoding]::utf8.getbytes($_.svg)))"; | |
} | |
} | |
$mxiconsjson=$mxicons|convertto-json -compress -depth 100 | |
$xml=[xml.xmldocument]::new() | |
$mxlibrary=$xml.createelement('mxlibrary') | |
$xml.appendchild($mxlibrary)|out-null | |
$mxlibrary.innertext=$mxiconsjson | |
$xml.outerxml|out-file drawio.xml | |
"Created drawio library of $([math]::round((gi drawio.xml).length/1024/1024, 1))MB" >> $env:GITHUB_STEP_SUMMARY | |
- name: Upload drawio library | |
uses: actions/upload-artifact@v4 | |
with: | |
name: drawio | |
compression-level: 9 | |
path: | | |
drawio.xml | |
- name: Compose Markdown | |
shell: pwsh | |
run: | | |
$icondata=gc icon-data.json|convertfrom-json | |
$markdown=$icondata|% ` | |
{"# Azure Icons`n`nExtracted from Azure Portal version ${{ steps.src.outputs.pageversion }} on $(get-date -f 'yyyy-MM-dd').`n`n[View icons](https://maskati.github.io/azure-icons/)`n`n[Open in draw.io](https://app.diagrams.net/?splash=0&clibs=Uhttps%3A%2F%2Fraw.githubusercontent.com%2Fmaskati%2Fazure-icons%2Frefs%2Fheads%2Fmain%2Fdrawio.xml)`n`nIcons [used with permission from Microsoft](https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/permissions).`n`n| Icon | Type |`n| --- | --- |"} ` | |
{"| [$($_.singularDisplayName -replace '\[|\]|\|','')](svg/$($_.name).svg) | $($_.resourceType -replace '/','/ ') |"} ` | |
{""}|out-string | |
$markdown|out-file README.md | |
"Created Markdown of $([math]::round((gi README.md).length/1024/1024, 1))MB" >> $env:GITHUB_STEP_SUMMARY | |
- name: Upload Markdown | |
uses: actions/upload-artifact@v4 | |
with: | |
name: markdown | |
compression-level: 9 | |
path: README.md | |
commit: | |
runs-on: ubuntu-22.04 | |
name: Commit | |
permissions: | |
actions: write # required for workflow_dispatch | |
contents: write # required for git push | |
needs: download | |
steps: | |
- name: Git checkout | |
uses: actions/checkout@v4 | |
- name: Download Azure Icons | |
uses: actions/download-artifact@v4 | |
with: | |
name: azure-icons | |
path: . | |
- name: Download markdown | |
uses: actions/download-artifact@v4 | |
with: | |
name: markdown | |
path: . | |
- name: Download drawio | |
uses: actions/download-artifact@v4 | |
with: | |
name: drawio | |
path: . | |
- name: Git config as workflow | |
run: | | |
git config user.name "GitHub Actions (${{ github.workflow }})" | |
git config user.email "<>" | |
- name: Git add and commit if changed | |
run: | | |
git add -u svg | |
git diff-index --quiet HEAD || \ | |
git add index.html README.md drawio.xml && \ | |
(git diff-index --quiet HEAD || git commit -m 'Update to version ${{ needs.download.outputs.pageversion }} with hash ${{ needs.download.outputs.confighash }}') | |
- name: Git push | |
id: push | |
run: | | |
git push origin ${{ github.ref_name }} --porcelain | grep -qF '[up to date]' && echo 'pushed=false' >> $GITHUB_OUTPUT || echo 'pushed=true' >> $GITHUB_OUTPUT | |
- name: Git tag | |
if: github.ref == 'refs/heads/main' && steps.push.outputs.pushed == 'true' | |
run: | | |
tag=$(date +'%Y-%m-%d') | |
git tag --force "$tag" | |
git push --force origin tag "$tag" | |
- name: Trigger publish | |
if: github.ref == 'refs/heads/main' && steps.push.outputs.pushed == 'true' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
github.rest.actions.createWorkflowDispatch({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
workflow_id: 'static.yml', | |
ref: 'main', | |
}) |