├── .github ├── renovate.json └── workflows │ ├── ci.yml │ ├── docker-publish.yml │ ├── init.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── README.md ├── authentication.md ├── commands │ ├── images.md │ ├── manifests.md │ ├── referrers.md │ ├── repositories.md │ ├── settings.md │ └── tags.md ├── platform-resolution.md └── settings.md ├── dredge-logo.png ├── global.json ├── src ├── .dockerignore ├── Dockerfile ├── Valleysoft.Dredge.Analyzers │ ├── SettingsPropertyGenerator.cs │ └── Valleysoft.Dredge.Analyzers.csproj ├── Valleysoft.Dredge.Tests │ ├── CompareLayersCommandTests.cs │ ├── DockerfileCommandTests.cs │ ├── TestData │ │ └── DockerfileCommand │ │ │ ├── fedora │ │ │ ├── expected-output-format.txt │ │ │ ├── expected-output-no-format.txt │ │ │ └── image.json │ │ │ ├── golang │ │ │ ├── expected-output-format.txt │ │ │ ├── expected-output-no-format.txt │ │ │ └── image.json │ │ │ ├── mariner │ │ │ ├── expected-output-format.txt │ │ │ ├── expected-output-no-format.txt │ │ │ └── image.json │ │ │ ├── openjdk │ │ │ ├── expected-output-format.txt │ │ │ ├── expected-output-no-format.txt │ │ │ └── image.json │ │ │ ├── python │ │ │ ├── expected-output-format.txt │ │ │ ├── expected-output-no-format.txt │ │ │ └── image.json │ │ │ └── windows │ │ │ ├── expected-output-format.txt │ │ │ ├── expected-output-no-format.txt │ │ │ └── image.json │ ├── TestHelper.cs │ ├── Usings.cs │ └── Valleysoft.Dredge.Tests.csproj ├── Valleysoft.Dredge.sln └── Valleysoft.Dredge │ ├── AppSettings.cs │ ├── CommandHelper.cs │ ├── Commands │ ├── CommandWithOptions.cs │ ├── Image │ │ ├── CompareCommand.cs │ │ ├── CompareFilesCommand.cs │ │ ├── CompareFilesOptions.cs │ │ ├── CompareLayersCommand.cs │ │ ├── CompareLayersOptions.cs │ │ ├── CompareOptionsBase.cs │ │ ├── DockerfileCommand.cs │ │ ├── DockerfileOptions.cs │ │ ├── ImageCommand.cs │ │ ├── InspectCommand.cs │ │ ├── InspectOptions.cs │ │ ├── OsCommand.cs │ │ ├── OsOptions.cs │ │ ├── SaveLayersCommand.cs │ │ └── SaveLayersOptions.cs │ ├── Manifest │ │ ├── DigestCommand.cs │ │ ├── DigestOptions.cs │ │ ├── GetCommand.cs │ │ ├── GetOptions.cs │ │ ├── ManifestCommand.cs │ │ ├── ResolveCommand.cs │ │ └── ResolveOptions.cs │ ├── OptionsBase.cs │ ├── PlatformOptionsBase.cs │ ├── Referrer │ │ ├── ListCommand.cs │ │ ├── ListOptions.cs │ │ └── ReferrerCommand.cs │ ├── RegistryCommandBase.cs │ ├── Repo │ │ ├── ListCommand.cs │ │ ├── ListOptions.cs │ │ └── RepoCommand.cs │ ├── Settings │ │ ├── ClearCacheCommand.cs │ │ ├── GetCommand.cs │ │ ├── GetOptions.cs │ │ ├── OpenCommand.cs │ │ ├── SetCommand.cs │ │ ├── SetOptions.cs │ │ └── SettingsCommand.cs │ └── Tag │ │ ├── ListCommand.cs │ │ ├── ListOptions.cs │ │ └── TagCommand.cs │ ├── CompareFilesOutput.cs │ ├── CompareLayersOutput.cs │ ├── CompareLayersResult.cs │ ├── DockerHubHelper.cs │ ├── DockerRegistryClientFactory.cs │ ├── DockerRegistryClientWrapper.cs │ ├── DredgeState.cs │ ├── FileHelper.cs │ ├── IDockerRegistryClient.cs │ ├── IDockerRegistryClientFactory.cs │ ├── ImageHelper.cs │ ├── ImageName.cs │ ├── JsonHelper.cs │ ├── LinuxOsInfo.cs │ ├── ManifestHelper.cs │ ├── Program.cs │ ├── RegistryHelper.cs │ ├── Valleysoft.Dredge.csproj │ └── WindowsOsInfo.cs └── version.txt /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices" 5 | ], 6 | "mode": "full" 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, dev ] 6 | pull_request: 7 | branches: [ main, dev ] 8 | 9 | defaults: 10 | run: 11 | working-directory: src 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 19 | 20 | - name: Setup .NET SDK 21 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 22 | 23 | - name: Install dependencies 24 | run: dotnet restore 25 | 26 | - name: Build 27 | run: dotnet build -c Release --no-restore 28 | 29 | - name: Test 30 | run: dotnet test --no-restore -v normal -c Release --results-directory test-results -l trx 31 | 32 | - name: Upload Test Results 33 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 34 | if: always() 35 | with: 36 | name: test-results 37 | path: src/test-results/* 38 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: [ main, dev ] 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | IMAGE_NAME: ${{ github.repository }} 10 | 11 | jobs: 12 | 13 | init: 14 | uses: ./.github/workflows/init.yml 15 | 16 | docker: 17 | name: Build Docker Image 18 | runs-on: ubuntu-latest 19 | needs: [ init ] 20 | 21 | steps: 22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 23 | 24 | - name: Log in to the Container registry 25 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 26 | with: 27 | registry: ${{ env.REGISTRY }} 28 | username: ${{ github.actor }} 29 | password: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Set up QEMU 32 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 33 | 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 36 | 37 | - name: Build and push 38 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 39 | with: 40 | context: ./src 41 | platforms: linux/amd64,linux/arm64 42 | push: ${{ github.event_name != 'pull_request' }} 43 | tags: > 44 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.init.outputs.product-version }}, 45 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.init.outputs.product-version-major }}, 46 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 47 | build-args: | 48 | PACKAGE_VERSION=${{ needs.init.outputs.product-version }} 49 | -------------------------------------------------------------------------------- /.github/workflows/init.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | outputs: 4 | product-version: 5 | value: ${{ jobs.init.outputs.product-version }} 6 | product-version-major: 7 | value: ${{ jobs.init.outputs.product-version-major }} 8 | 9 | jobs: 10 | 11 | init: 12 | name: Initialize 13 | runs-on: ubuntu-latest 14 | 15 | outputs: 16 | product-version: ${{ steps.version.outputs.product-version }} 17 | product-version-major: ${{ steps.version.outputs.product-version-major }} 18 | 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 21 | 22 | - name: Get Version 23 | id: version 24 | working-directory: ./ 25 | run: | 26 | productVersion=$(cat version.txt) 27 | majorVersion=$(echo "$productVersion" | cut -d'.' -f1) 28 | echo "product-version=$productVersion" >> $GITHUB_OUTPUT 29 | echo "product-version-major=$majorVersion" >> $GITHUB_OUTPUT 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | confirmVersionIsSet: 7 | description: 'Confirm version is set' 8 | type: boolean 9 | 10 | defaults: 11 | run: 12 | working-directory: src 13 | 14 | jobs: 15 | 16 | init: 17 | uses: ./.github/workflows/init.yml 18 | 19 | exe: 20 | name: Build executables 21 | runs-on: ubuntu-latest 22 | needs: init 23 | 24 | strategy: 25 | matrix: 26 | rid: 27 | - win-x64 28 | - win-arm64 29 | - osx-x64 30 | - osx-arm64 31 | - linux-x64 32 | - linux-arm64 33 | - linux-musl-x64 34 | - linux-musl-arm64 35 | 36 | steps: 37 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 38 | 39 | - name: Setup .NET SDK 40 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 41 | 42 | - name: Install dependencies 43 | working-directory: src/Valleysoft.Dredge 44 | run: dotnet restore --runtime ${{ matrix.rid }} 45 | 46 | - name: Publish 47 | working-directory: src/Valleysoft.Dredge 48 | env: 49 | PACKAGE_VERSION: ${{ needs.init.outputs.product-version }} 50 | run: dotnet publish -f net9.0 -c Release -p:Version=$PACKAGE_VERSION --no-restore -o ${{ github.workspace }}/publish --runtime ${{ matrix.rid }} --no-self-contained 51 | 52 | - name: Rename output 53 | run: | 54 | if [[ "${{ matrix.rid }}" == *"win"* ]]; then 55 | dredgeExt=".exe" 56 | else 57 | dredgeExt="" 58 | fi 59 | 60 | exeName="dredge-${{ needs.init.outputs.product-version }}-${{ matrix.rid }}${dredgeExt}" 61 | echo "EXE_NAME=${exeName}" >> $GITHUB_ENV 62 | mv ${{ github.workspace }}/publish/dredge${dredgeExt} ${{ github.workspace }}/publish/${exeName} 63 | 64 | - name: Generate checksum 65 | run: sha256sum ${EXE_NAME} >${EXE_NAME}.sha256sum 66 | working-directory: ${{ github.workspace }}/publish 67 | 68 | - name: Save build binaries 69 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 70 | with: 71 | name: dredge-binaries-${{ matrix.rid }} 72 | path: ${{ github.workspace }}/publish 73 | 74 | save-exes: 75 | name: Save executables 76 | needs: exe 77 | runs-on: ubuntu-latest 78 | 79 | steps: 80 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 81 | 82 | - name: Download build binaries 83 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 84 | with: 85 | path: ${{ github.workspace }}/publish 86 | 87 | - name: Move all files 88 | run: | 89 | mv ${{ github.workspace }}/publish/dredge-binaries-*/* ${{ github.workspace }}/publish 90 | rm -r ${{ github.workspace }}/publish/dredge-binaries-* 91 | 92 | - name: Save build binaries 93 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 94 | with: 95 | name: dredge-binaries 96 | path: ${{ github.workspace }}/publish 97 | 98 | nuget: 99 | name: Publish NuGet Package 100 | runs-on: ubuntu-latest 101 | needs: [ init, save-exes ] 102 | 103 | steps: 104 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 105 | 106 | - name: Setup .NET SDK 107 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 108 | 109 | - name: Install dependencies 110 | run: dotnet restore 111 | 112 | - name: Build 113 | env: 114 | PACKAGE_VERSION: ${{ needs.init.outputs.product-version }} 115 | run: dotnet build -c Release -p:Version=$PACKAGE_VERSION --no-restore Valleysoft.Dredge 116 | 117 | - name: Pack 118 | env: 119 | PACKAGE_VERSION: ${{ needs.init.outputs.product-version }} 120 | run: dotnet pack -c Release -p:Version=$PACKAGE_VERSION Valleysoft.Dredge -p:IsPack=true 121 | 122 | - name: Publish Package 123 | run: dotnet nuget push "Valleysoft.Dredge/bin/Release/*.nupkg" -k ${{secrets.NUGET_ORG_API_KEY}} -s https://nuget.org 124 | 125 | docker: 126 | uses: ./.github/workflows/docker-publish.yml 127 | needs: [ nuget ] 128 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | launchSettings.json 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # StyleCop 66 | StyleCopReport.xml 67 | 68 | # Files built by Visual Studio 69 | *_i.c 70 | *_p.c 71 | *_h.h 72 | *.ilk 73 | *.meta 74 | *.obj 75 | *.iobj 76 | *.pch 77 | *.pdb 78 | *.ipdb 79 | *.pgc 80 | *.pgd 81 | *.rsp 82 | *.sbr 83 | *.tlb 84 | *.tli 85 | *.tlh 86 | *.tmp 87 | *.tmp_proj 88 | *_wpftmp.csproj 89 | *.log 90 | *.vspscc 91 | *.vssscc 92 | .builds 93 | *.pidb 94 | *.svclog 95 | *.scc 96 | 97 | # Chutzpah Test files 98 | _Chutzpah* 99 | 100 | # Visual C++ cache files 101 | ipch/ 102 | *.aps 103 | *.ncb 104 | *.opendb 105 | *.opensdf 106 | *.sdf 107 | *.cachefile 108 | *.VC.db 109 | *.VC.VC.opendb 110 | 111 | # Visual Studio profiler 112 | *.psess 113 | *.vsp 114 | *.vspx 115 | *.sap 116 | 117 | # Visual Studio Trace Files 118 | *.e2e 119 | 120 | # TFS 2012 Local Workspace 121 | $tf/ 122 | 123 | # Guidance Automation Toolkit 124 | *.gpState 125 | 126 | # ReSharper is a .NET coding add-in 127 | _ReSharper*/ 128 | *.[Rr]e[Ss]harper 129 | *.DotSettings.user 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # NuGet Symbol Packages 189 | *.snupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/[Pp]ackages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/[Pp]ackages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/[Pp]ackages/repositories.config 196 | # NuGet v3's project.json files produces more ignorable files 197 | *.nuget.props 198 | *.nuget.targets 199 | 200 | # Microsoft Azure Build Output 201 | csx/ 202 | *.build.csdef 203 | 204 | # Microsoft Azure Emulator 205 | ecf/ 206 | rcf/ 207 | 208 | # Windows Store app package directories and files 209 | AppPackages/ 210 | BundleArtifacts/ 211 | Package.StoreAssociation.xml 212 | _pkginfo.txt 213 | *.appx 214 | *.appxbundle 215 | *.appxupload 216 | 217 | # Visual Studio cache files 218 | # files ending in .cache can be ignored 219 | *.[Cc]ache 220 | # but keep track of directories ending in .cache 221 | !?*.[Cc]ache/ 222 | 223 | # Others 224 | ClientBin/ 225 | ~$* 226 | *~ 227 | *.dbmdl 228 | *.dbproj.schemaview 229 | *.jfm 230 | *.pfx 231 | *.publishsettings 232 | orleans.codegen.cs 233 | 234 | # Including strong name files can present a security risk 235 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 236 | #*.snk 237 | 238 | # Since there are multiple workflows, uncomment next line to ignore bower_components 239 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 240 | #bower_components/ 241 | 242 | # RIA/Silverlight projects 243 | Generated_Code/ 244 | 245 | # Backup & report files from converting an old project file 246 | # to a newer Visual Studio version. Backup files are not needed, 247 | # because we have git ;-) 248 | _UpgradeReport_Files/ 249 | Backup*/ 250 | UpgradeLog*.XML 251 | UpgradeLog*.htm 252 | ServiceFabricBackup/ 253 | *.rptproj.bak 254 | 255 | # SQL Server files 256 | *.mdf 257 | *.ldf 258 | *.ndf 259 | 260 | # Business Intelligence projects 261 | *.rdl.data 262 | *.bim.layout 263 | *.bim_*.settings 264 | *.rptproj.rsuser 265 | *- [Bb]ackup.rdl 266 | *- [Bb]ackup ([0-9]).rdl 267 | *- [Bb]ackup ([0-9][0-9]).rdl 268 | 269 | # Microsoft Fakes 270 | FakesAssemblies/ 271 | 272 | # GhostDoc plugin setting file 273 | *.GhostDoc.xml 274 | 275 | # Node.js Tools for Visual Studio 276 | .ntvs_analysis.dat 277 | node_modules/ 278 | 279 | # Visual Studio 6 build log 280 | *.plg 281 | 282 | # Visual Studio 6 workspace options file 283 | *.opt 284 | 285 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 286 | *.vbw 287 | 288 | # Visual Studio LightSwitch build output 289 | **/*.HTMLClient/GeneratedArtifacts 290 | **/*.DesktopClient/GeneratedArtifacts 291 | **/*.DesktopClient/ModelManifest.xml 292 | **/*.Server/GeneratedArtifacts 293 | **/*.Server/ModelManifest.xml 294 | _Pvt_Extensions 295 | 296 | # Paket dependency manager 297 | .paket/paket.exe 298 | paket-files/ 299 | 300 | # FAKE - F# Make 301 | .fake/ 302 | 303 | # CodeRush personal settings 304 | .cr/personal 305 | 306 | # Python Tools for Visual Studio (PTVS) 307 | __pycache__/ 308 | *.pyc 309 | 310 | # Cake - Uncomment if you are using it 311 | # tools/** 312 | # !tools/packages.config 313 | 314 | # Tabs Studio 315 | *.tss 316 | 317 | # Telerik's JustMock configuration file 318 | *.jmconfig 319 | 320 | # BizTalk build output 321 | *.btp.cs 322 | *.btm.cs 323 | *.odx.cs 324 | *.xsd.cs 325 | 326 | # OpenCover UI analysis results 327 | OpenCover/ 328 | 329 | # Azure Stream Analytics local run output 330 | ASALocalRun/ 331 | 332 | # MSBuild Binary and Structured Log 333 | *.binlog 334 | 335 | # NVidia Nsight GPU debugger configuration file 336 | *.nvuser 337 | 338 | # MFractors (Xamarin productivity tool) working folder 339 | .mfractor/ 340 | 341 | # Local History for Visual Studio 342 | .localhistory/ 343 | 344 | # BeatPulse healthcheck temp database 345 | healthchecksdb 346 | 347 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 348 | MigrationBackup/ 349 | 350 | # Ionide (cross platform F# VS Code tools) working folder 351 | .ionide/ 352 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome. Submit a pull request or open an issue as necessary. 4 | 5 | ## Developer Prerequisites 6 | 7 | * [Docker](https://docs.docker.com/get-docker/) 8 | * [.NET 6.0 SDK](https://docs.microsoft.com/dotnet/core/install/) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matthew Thalman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Dredge: A Container Registry Client CLI 4 | 5 | Dredge is a CLI built on .NET that provides a simple way to execute commands on a container registry's HTTP API. Currently, only read operations are supported. 6 | 7 | ## Features 8 | 9 | * Access to raw JSON data from the registry's HTTP API. 10 | * Extended, derived data such as [image configuration](docs/commands/images.md#inspect-image-configuration), [OS information](docs/commands/images.md#image-os-information), and comparison of [layers](docs/commands/images.md#compare-image-layers) and [files](docs/commands/images.md#compare-image-files). 11 | 12 | ### Documentation 13 | 14 | The main documentation is in the [docs](docs) directory. 15 | 16 | ## Install 17 | 18 | ### Installing as a standalone executable 19 | 20 | Download the desired executable from the [release page](https://github.com/mthalman/dredge/releases). 21 | 22 | Prerequisites: 23 | * [.NET 9 runtime](https://dotnet.microsoft.com/download/dotnet/9.0) 24 | 25 | ### Running as a container 26 | 27 | ```shell 28 | docker run --rm ghcr.io/mthalman/dredge --help 29 | ``` 30 | 31 | ### Installing as a .NET global tool 32 | 33 | ```console 34 | > dotnet tool install -g Valleysoft.Dredge 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Dredge Docs 2 | 3 | * Commands 4 | * [`image`](commands/images.md) 5 | * [`manifest`](commands/manifests.md) 6 | * [`repo`](commands/repositories.md) 7 | * [`tag`](commands/tags.md) 8 | * [`settings`](commands/settings.md) 9 | * [Authentication](authentication.md) 10 | * [Settings file](settings.md) 11 | * [Platform resolution](platform-resolution.md) 12 | -------------------------------------------------------------------------------- /docs/authentication.md: -------------------------------------------------------------------------------- 1 | # Registry Authentication 2 | 3 | For container registries requiring authentication, Dredge can make use of credentials stored in your environment via the `docker login` command. 4 | Alternatively, you can set the `DREDGE_TOKEN` environment variable to an OAuth bearer token or set the `DREDGE_USERNAME` and `DREDGE_PASSWORD` environment variables if you have credentials. 5 | Dredge will look for the environment variables first and fall back to any `docker login` credentials if they exist. 6 | -------------------------------------------------------------------------------- /docs/commands/images.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | Sub-commands: 4 | 5 | * [`inspect`](#inspect-image-configuration) - Inspects an image configuration 6 | * [`os`](#image-os-information) - Image OS information 7 | * [`compare layers`](#compare-image-layers) - Compares the layers of two images 8 | * [`compare files`](#compare-image-files) - Compares the files of two images 9 | * [`save layers`](#save-layers) - Saves the layers of an image to disk 10 | * [`dockerfile`](#generate-dockerfile) - Generates a Dockerfile that represents the image 11 | 12 | ## Inspect Image Configuration 13 | 14 | The `image inspect` command returns the image configuration of the specified image name. 15 | 16 | ```console 17 | > dredge image inspect amd64/ubuntu:22.04 18 | { 19 | "architecture": "amd64", 20 | "config": { 21 | "Hostname": "", 22 | "Domainname": "", 23 | "User": "", 24 | "AttachStdin": false, 25 | "AttachStdout": false, 26 | --- --- 27 | "os": "linux", 28 | "rootfs": { 29 | "type": "layers", 30 | "diff_ids": [ 31 | "sha256:f4a670ac65b68f8757aea863ac0de19e627c0ea57165abad8094eae512ca7dad" 32 | ] 33 | } 34 | } 35 | ``` 36 | 37 | ## Image OS Information 38 | 39 | The `image os` command returns information about the OS of the specified image name. Supports both Linux and Windows images. 40 | 41 | ### Linux 42 | 43 | ```console 44 | > dredge image os amd64/ubuntu:22.04 45 | { 46 | "PRETTY_NAME": "Ubuntu 22.04.1 LTS", 47 | "NAME": "Ubuntu", 48 | "ID": "ubuntu", 49 | "ID_LIKE": [ 50 | "debian" 51 | ], 52 | "VERSION": "22.04.1 LTS (Jammy Jellyfish)", 53 | "VERSION_ID": "22.04", 54 | "VERSION_CODENAME": "jammy", 55 | "HOME_URL": "https://www.ubuntu.com/", 56 | "SUPPORT_URL": "https://help.ubuntu.com/", 57 | "BUG_REPORT_URL": "https://bugs.launchpad.net/ubuntu/", 58 | "PRIVACY_POLICY_URL": "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" 59 | } 60 | ``` 61 | 62 | ### Windows 63 | 64 | ```console 65 | > dredge image os mcr.microsoft.com/windows/nanoserver:ltsc2022-amd64 66 | { 67 | "Type": "Nano Server", 68 | "Version": "10.0.20348.1249" 69 | } 70 | ``` 71 | 72 | ### Compare Image Layers 73 | 74 | The `image compare layers` command compares the layers of two images. 75 | 76 | There are a variety of output options available: 77 | 78 | * SideBySide (default): Displays the comparison side-by-side in a table layout 79 | * Inline: Displays the comparison in an inline fashion 80 | * JSON: Returns a JSON representation of the comparison, including summary analysis 81 | 82 | There's also a `--history` option to include the layer history information associated with the given layer. 83 | 84 | By default, the comparison makes use of green and red colors to indicate differences. For accessibility purposes, you can choose to use the `--no-color` option which will disable the use of these colors and use textual means to indicate diffs instead. 85 | 86 | ```diff 87 | $ dredge image compare layers --output inline amd64/node:19.1-alpine amd64/node:19.2-alpine 88 | sha256:ca7dd9ec2225f2385955c43b2379305acd51543c28cf1d4e94522b3d94cce3ce 89 | - sha256:4487691952c066cb3964b94606825bc96c698377909c7d74c889fd12e24e36a7 90 | + sha256:bfebca31f7556839677aca8626941ec4be0d5e2a1a59f1bd991807828de37167 91 | - sha256:206c50ffab466a0ed68db742d6d2015abcedd0a0b2500babb1938ce2a272b425 92 | + sha256:cc0056ab0c4160f34cd7046016f9aa6d1d14c206f61768b34efa69c45c38a0cb 93 | - sha256:f6d4361cf153f2e83958f504356eef6e3d041eb3c4d23da466480ee2dfe656ae 94 | + sha256:6e25476b6324255c964f6b86e587d867e79046e94933123d0f1312dbddfe87b7 95 | ``` 96 | 97 | ``` 98 | > dredge image compare layers --history --no-color mcr.microsoft.com/dotnet/runtime:6.0.5-jammy-amd64 mcr.microsoft.com/dotnet/runtime:6.0.6-jammy-amd64 99 | ┌──────────────────────────────────────────────────────────────────────────┬───────────┬─────────────────────────────────────────────────────────────────────────┐ 100 | │ mcr.microsoft.com/dotnet/runtime:6.0.5-jammy-amd64 │ Compare │ mcr.microsoft.com/dotnet/runtime:6.0.6-jammy-amd64 │ 101 | ├──────────────────────────────────────────────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────┤ 102 | │ sha256:405f018f9d1d0f351c196b841a7c7f226fb8ea448acd6339a9ed8741600275a2 │ Equal │ sha256:405f018f9d1d0f351c196b841a7c7f226fb8ea448acd6339a9ed8741600275a2 │ 103 | │ /bin/sh -c #(nop) ADD │ │ /bin/sh -c #(nop) ADD │ 104 | │ file:11157b07dde10107f3f6f2b892c869ea83868475d5825167b5f466a7e410eb05 in │ │ file:11157b07dde10107f3f6f2b892c869ea83868475d5825167b5f466a7e410eb05 │ 105 | │ / │ │ in / │ 106 | │ │ │ │ 107 | │ │ Equal │ │ 108 | │ /bin/sh -c #(nop) CMD ["bash"] │ │ /bin/sh -c #(nop) CMD ["bash"] │ 109 | │ │ │ │ 110 | │ sha256:7f5199084fb2409a567d45cbe1eebb7ad2bb92d2f2eeac1f9d7d1521b6529da5 │ Not Equal │ sha256:1d6b7ed86f8a0efb7b44af3ac71d881ea686c7e26f2bf9b509ffcee50d503a44 │ 111 | │ /bin/sh -c apt-get update && apt-get install -y │ │ /bin/sh -c apt-get update && apt-get install -y │ 112 | │ --no-install-recommends ca-certificates libc6 │ │ --no-install-recommends ca-certificates libc6 │ 113 | │ libgcc1 libgssapi-krb5-2 libicu70 libssl3 │ │ libgcc1 libgssapi-krb5-2 libicu70 libssl3 │ 114 | │ libstdc++6 zlib1g && rm -rf /var/lib/apt/lists/* │ │ libstdc++6 zlib1g && rm -rf /var/lib/apt/lists/* │ 115 | │ │ │ │ 116 | │ │ Equal │ │ 117 | │ /bin/sh -c #(nop) ENV ASPNETCORE_URLS=http://+:80 │ │ /bin/sh -c #(nop) ENV ASPNETCORE_URLS=http://+:80 │ 118 | │ DOTNET_RUNNING_IN_CONTAINER=true │ │ DOTNET_RUNNING_IN_CONTAINER=true │ 119 | │ │ │ │ 120 | │ │ Not Equal │ │ 121 | │ /bin/sh -c #(nop) ENV DOTNET_VERSION=6.0.5 │ │ /bin/sh -c #(nop) ENV DOTNET_VERSION=6.0.6 │ 122 | │ │ │ │ 123 | │ sha256:ae2c6691208b45534916003bf6e5607998bab42aa923dc5f1e21fc244f0a9832 │ Not Equal │ sha256:18a715d5177a41204dd062b5760565bd282526e22063ab158b4180833f5a5156 │ 124 | │ /bin/sh -c #(nop) COPY │ │ /bin/sh -c #(nop) COPY │ 125 | │ dir:49b45e3ccadd0521a7513b91e6cb00a52ff23f9e8004ce74c832042e93fe7e33 in │ │ dir:fb7195f4bc42fce62a7104cc5ef211701a1267b4666b445b59f649b0f86ecaa6 in │ 126 | │ /usr/share/dotnet │ │ /usr/share/dotnet │ 127 | │ │ │ │ 128 | │ sha256:114810c4073fb2a42557832ebfa76ec9a0f3ddcd13edf20b9f6d690f0d0be720 │ Not Equal │ sha256:6adc839fa9c17fc4a0f1965aa58b446f6531b4b926995080404321a223ce82b2 │ 129 | │ /bin/sh -c ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet │ │ /bin/sh -c ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet │ 130 | └──────────────────────────────────────────────────────────────────────────┴───────────┴─────────────────────────────────────────────────────────────────────────┘ 131 | ``` 132 | 133 | ### Compare Image Files 134 | 135 | The `image compare files` command provides a way to compare the file contents of two images. 136 | 137 | Example usage: 138 | 139 | ```shell 140 | > dredge image compare files amd64/node:19.1-alpine amd64/node:19.2-alpine 141 | ``` 142 | 143 | The layers of the images are downloaded and applied (squashed) to produce a local representation of the file system for each image. 144 | 145 | The actual comparison of the files requires an external tool provided by the user. This external comparison tool is configured with Dredge's settings.json file. 146 | 147 | Example: 148 | 149 | ```json 150 | { 151 | "fileCompareTool": { 152 | "exePath": "C:\\Program Files\\Beyond Compare 4\\BCompare.exe", 153 | "args": "{0} {1}" 154 | } 155 | } 156 | ``` 157 | 158 | In addition to comparing the entire image, you can also include a subset of the image by specifying a layer index in the command options. This will only apply the layers of the image up to the specified index. For example, the following command compares only the first two layers of the images: 159 | 160 | ```shell 161 | > dredge image compare files amd64/node:19.1-alpine amd64/node:19.2-alpine --base-layer-index 1 --target-layer-index 1 162 | ``` 163 | 164 | This option also enables you to compare the layers of a single image. This command compares the difference between the 2nd and 3rd layer of the `amd64/node:19.1-alpine` image: 165 | 166 | ```shell 167 | > dredge image compare files amd64/node:19.1-alpine amd64/node:19.1-alpine --base-layer-index 1 --target-layer-index 2 168 | ``` 169 | 170 | ### Save Layers 171 | 172 | The `image save-layers` command provides a way to save the extracted layers of an image to disk. 173 | 174 | Example usage: 175 | 176 | ```shell 177 | > dredge image save-layers amd64/node:19.2-alpine out/layers/node 178 | ``` 179 | 180 | By default, the layers of the image are squashed and saved to a single directory. The `--no-squash` option can be used to disable this behavior and save the layers as individual directories. 181 | 182 | If you want to target a specific layer, you can use the `--layer-index` option. This will only save the specified layer (and any layers that it depends on if squashing is being applied). 183 | 184 | ## Generate Dockerfile 185 | 186 | The `image dockerfile` command generates a Dockerfile that represents an image. 187 | 188 | By default, it uses a set of heuristics to generate line breaks for a Dockerfile instruction to make it more readable. This can be disabled with the `--no-format` option. The output also uses syntax coloring by default for readability. This can be disabled with the `--no-color` option. 189 | -------------------------------------------------------------------------------- /docs/commands/manifests.md: -------------------------------------------------------------------------------- 1 | # Manifests 2 | 3 | Sub-commands: 4 | 5 | * [`get`](#query-manifest) - Gets a manifest 6 | * [`digest`](#query-digest) - Gets the digest of a manifest 7 | * [`resolve`](#resolve-manifest) - Resolves a manifest 8 | 9 | ## Query Manifest 10 | 11 | Returns the manifest of the specified name. 12 | 13 | ```console 14 | > dredge manifest get ubuntu:22.04 15 | { 16 | "manifests": [ 17 | { 18 | "mediaType": "application/vnd.docker.distribution.manifest.v2+json", 19 | "size": 529, 20 | "digest": "sha256:817cfe4672284dcbfee885b1a66094fd907630d610cab329114d036716be49ba", 21 | "platform": { 22 | "architecture": "amd64", 23 | "os": "linux", 24 | "os.version": null, 25 | "os.features": [], 26 | "variant": null, 27 | "features": [] 28 | } 29 | }, 30 | --- --- 31 | { 32 | "mediaType": "application/vnd.docker.distribution.manifest.v2+json", 33 | "size": 529, 34 | "digest": "sha256:75f39282185d9d952d5d19491a0c98ed9f798b0251c6d9a026e5b71cc2bf4de3", 35 | "platform": { 36 | "architecture": "s390x", 37 | "os": "linux", 38 | "os.version": null, 39 | "os.features": [], 40 | "variant": null, 41 | "features": [] 42 | } 43 | } 44 | ], 45 | "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", 46 | "schemaVersion": 2 47 | } 48 | ``` 49 | 50 | ## Query Digest 51 | 52 | Returns the digest of the specified name. 53 | 54 | ```console 55 | > dredge manifest digest ubuntu:22.04 56 | sha256:4b1d0c4a2d2aaf63b37111f34eb9fa89fa1bf53dd6e4ca954d47caebca4005c2 57 | ``` 58 | 59 | ## Resolve Manifest 60 | 61 | Resolves a manifest to a target platform's fully-qualified image digest. This is useful when you want to get the image digest of a specific platform from a multi-arch tag. 62 | 63 | ```console 64 | > dredge manifest resolve ubuntu:22.04 --os linux --arch amd64 65 | library/ubuntu@sha256:817cfe4672284dcbfee885b1a66094fd907630d610cab329114d036716be49ba 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/commands/referrers.md: -------------------------------------------------------------------------------- 1 | # Referrers 2 | 3 | Sub-commands: 4 | 5 | * [`list`](#query-referrers) - Lists the referrers to a manifest 6 | 7 | ## Query Referrers 8 | 9 | Returns the referrers to the specified manifest. 10 | 11 | ```console 12 | > dredge referrer list mcr.microsoft.com/dotnet/core/sdk:latest 13 | { 14 | "manifests": [ 15 | { 16 | "mediaType": "application/vnd.oci.image.manifest.v1+json", 17 | "digest": "sha256:551e9aa2046071e51b1611a7e85f85af3d2cc6841935cc176a931de4194ecdc1", 18 | "size": 788, 19 | "urls": [], 20 | "annotations": { 21 | "org.opencontainers.image.created": "2024-08-13T14:20:19Z", 22 | "vnd.microsoft.artifact.lifecycle.end-of-life.date": "2022-12-13" 23 | }, 24 | "artifactType": "application/vnd.microsoft.artifact.lifecycle" 25 | } 26 | ], 27 | "annotations": {}, 28 | "schemaVersion": 2, 29 | "mediaType": "application/vnd.oci.image.index.v1+json" 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/commands/repositories.md: -------------------------------------------------------------------------------- 1 | # Repositories 2 | 3 | Sub-commands: 4 | 5 | * [`list`](#query-repositories) - Lists the repositories in a registry 6 | 7 | ## Query Repositories 8 | 9 | Returns the list of repositories from the specified registry. 10 | 11 | > Not supported for Docker Hub. 12 | 13 | ```console 14 | > dredge repo list mcr.microsoft.com 15 | [ 16 | "acc/samples/acc-perl", 17 | "acc/samples/attestation-inproc", 18 | "acc/samples/attestation-outproc", 19 | "acc/samples/attested-tls-inproc", 20 | "acc/samples/attested-tls-outproc", 21 | --- --- 22 | "windows/servercore/iis", 23 | "windows/servercore/iis/insider", 24 | "windows/servercore/insider", 25 | "windowsprotocoltestsuites", 26 | "wwllab/skills/skills-extractor-cognitive-search" 27 | ] 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/commands/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | Sub-commands: 4 | 5 | * [`open`](#open-settings) - Opens the Dredge settings file 6 | * [`get`](#get-setting) - Gets the value of a setting 7 | * [`set`](#set-setting) - Sets the value of a setting 8 | * [`clear-cache`](#clear-cache) - Deletes the cached files used by Dredge 9 | 10 | ## Open Settings 11 | 12 | The `settings open` command opens the Dredge settings file in the default associated program if it can. 13 | Otherwise, it outputs the path to the settings file. 14 | 15 | ## Get Setting 16 | 17 | The `settings get` command gets the value of a setting from the Dredge settings file. 18 | It takes a single argument as the name of the setting to get. 19 | Because the settings in the settings file are hierarchical and represented as JSON, setting names use a dot notation to separate the names to access the desired setting. 20 | For example: `dredge settings get fileCompareTool.exePath` gets the executable path of the file compare tool from the settings file. 21 | 22 | ## Set Setting 23 | 24 | The `settings set` command sets the value of a setting from the Dredge settings file. 25 | It takes the name of the setting to set followed by the value to set it to. 26 | Because the settings in the settings file are hierarchical and represented as JSON, setting names use a dot notation to separate the names to access the desired setting. 27 | For example: `dredge settings set platform.os linux` sets the default platform OS to "linux". 28 | 29 | ## Clear Cache 30 | 31 | The `settings clear-cache` command deletes the local cache of layer data stored in the temporary directory. 32 | -------------------------------------------------------------------------------- /docs/commands/tags.md: -------------------------------------------------------------------------------- 1 | # Tags 2 | 3 | Sub-commands: 4 | 5 | * [`list`](#query-tags) - Lists the tags in a repository 6 | 7 | ## Query Tags 8 | 9 | Returns the tags associated with the specified repository. 10 | 11 | ```console 12 | > dredge tag list ubuntu 13 | [ 14 | "10.04", 15 | "12.04", 16 | "12.04.5", 17 | "12.10", 18 | "13.04", 19 | --- --- 20 | "zesty-20170703", 21 | "zesty-20170913", 22 | "zesty-20170915", 23 | "zesty-20171114", 24 | "zesty-20171122" 25 | ] 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/platform-resolution.md: -------------------------------------------------------------------------------- 1 | # Platform Resolution 2 | 3 | For [certain operations](#operations-that-use-platform-resolution), Dredge needs to resolve a platform to a specific image manifest if a multi-arch/multi-platform tag is provided as input. 4 | For example, the `alpine:latest` tag points to different images for different architectures. 5 | For these commands, Dredge needs to know which image to use for the operation. 6 | If it's unable to resolve the platform, it will fail with an error. 7 | You can influence the platform resolution by setting the command's platform options or by setting the global platform settings. 8 | 9 | ## Operations that use platform resolution 10 | 11 | The following operations make use of platform resolution: 12 | 13 | * [`manifest resolve`](commands/manifests.md#resolve-manifest) 14 | * All [`image`](commands/images.md) sub-commands 15 | 16 | ## Platform options 17 | 18 | The following [platform options](https://github.com/mthalman/dredge/pull/52) can be used to influence platform resolution: 19 | 20 | * `--os`: The operating system of the platform ("linux" or "windows"). 21 | * `--os-version`: The operating system version of the platform. This is usually only relevant for Windows images but may be relevant for Linux images in some rare cases. 22 | * `--arch`: The architecture of the platform (e.g. "amd64", "arm", "arm64"). 23 | 24 | ## Global platform settings 25 | 26 | [Global platform settings](https://github.com/mthalman/dredge/pull/54) allow you to statically define platform settings in the Dredge settings file that will be used for all operations that use platform resolution. 27 | The same platform options can be used as described in the previous section. 28 | 29 | Here's the configuration of these global platform settings: 30 | 31 | ```json 32 | { 33 | "platform": { 34 | "os": "", 35 | "osVersion": "", 36 | "arch": "" 37 | } 38 | } 39 | ``` 40 | 41 | You can set these values by using the [`settings set`](commands/settings.md#set-settings) command: 42 | 43 | Example: 44 | 45 | ```console 46 | dredge settings set platform.os linux 47 | ``` 48 | 49 | ## Platform resolution order 50 | 51 | Since the platform options and global platform settings can be used to influence platform resolution, it's important to understand the order in which these settings are applied. 52 | 53 | Platform options provided in the call to the command take precedence over global platform settings. 54 | If a platform option is not provided in the call to the command, the global platform setting is used. 55 | 56 | It's not always necessary to set all of the platform values in order to successfully resolve the platform. 57 | For example, many images are only available for Linux. 58 | In that case, it's not necessary to set the `os` value because it doesn't reduce the amount of available platforms. 59 | For Linux, it's often enough to just set the `arch` value. 60 | As long as the provided platform values reduce the number of available platforms to a single platform, the platform resolution will succeed. 61 | -------------------------------------------------------------------------------- /docs/settings.md: -------------------------------------------------------------------------------- 1 | # Settings File 2 | 3 | Dredge uses a settings file to store configuration information. The settings file is a JSON file named `settings.json` that is located in the following location: 4 | 5 | * Windows: `%LOCALAPPDATA%\Valleysoft.Dredge\settings.json` 6 | * Linux: `$HOME/.local/share/Valleysoft.Dredge/settings.json` 7 | * Mac: `$HOME/Library/Application Support/Valleysoft.Dredge/settings.json` 8 | 9 | Dredge will create the settings file automatically when it's needed. 10 | 11 | The [`settings`](commands/settings.md) command can be used to manipulate the settings file. 12 | 13 | ## Schema 14 | 15 | ```json 16 | { 17 | "fileCompareTool": { 18 | "exePath": "", 19 | "args": "" 20 | }, 21 | "platform": { 22 | "os": "", 23 | "osVersion": "", 24 | "arch": "" 25 | } 26 | } 27 | ``` 28 | 29 | See [`image compare files`](commands/images.md#compare-image-files) for more information about the `fileCompareTool` setting. 30 | 31 | See [platform resolution](platform-resolution.md) for more information about the `platform` setting. 32 | -------------------------------------------------------------------------------- /dredge-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthalman/dredge/f5f20754c8916204d74ddf87d9a6c02b7c38228c/dredge-logo.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.300", 4 | "rollForward": "latestPatch", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/.dockerignore: -------------------------------------------------------------------------------- 1 | # directories 2 | **/bin/ 3 | **/obj/ 4 | **/out/ 5 | 6 | # files 7 | Dockerfile* 8 | **/*.trx 9 | **/*.md 10 | **/*.ps1 11 | **/*.cmd 12 | **/*.sh 13 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-noble@sha256:58fa5442c6da3bd654cab866fd6668de2713769511e412a3aa23c14368b84b16 AS build 2 | 3 | ARG TARGETARCH 4 | ARG PACKAGE_VERSION 5 | 6 | WORKDIR /source 7 | 8 | COPY Valleysoft.Dredge/*.csproj Valleysoft.Dredge/ 9 | COPY Valleysoft.Dredge.Analyzers/*.csproj Valleysoft.Dredge.Analyzers/ 10 | RUN dotnet restore -a $TARGETARCH Valleysoft.Dredge/*.csproj 11 | 12 | COPY Valleysoft.Dredge/ Valleysoft.Dredge/ 13 | COPY Valleysoft.Dredge.Analyzers/ Valleysoft.Dredge.Analyzers/ 14 | RUN dotnet publish Valleysoft.Dredge/*.csproj -f net9.0 -o /app -a $TARGETARCH --no-self-contained /p:Version=$PACKAGE_VERSION --no-restore 15 | 16 | 17 | FROM mcr.microsoft.com/dotnet/runtime:9.0-noble-chiseled@sha256:bd4288d187eac2d9753e4623e0466b9ceec2b340254a640858d3ebb1b25afbac 18 | WORKDIR /app 19 | COPY --from=build /app . 20 | ENTRYPOINT ["./dredge"] 21 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Analyzers/SettingsPropertyGenerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.CodeAnalysis.Text; 5 | using System.Collections.Immutable; 6 | using System.Text; 7 | 8 | namespace Valleysoft.Dredge.Analyzers; 9 | 10 | [Generator] 11 | public class SettingsSourceGenerator : IIncrementalGenerator 12 | { 13 | public void Initialize(IncrementalGeneratorInitializationContext context) 14 | { 15 | // Register a syntax receiver that will be created for each generation pass 16 | IncrementalValuesProvider classDeclarations = context.SyntaxProvider 17 | .CreateSyntaxProvider( 18 | predicate: static (s, _) => IsSettingsClass(s), 19 | transform: static (ctx, _) => GetClassDeclaration(ctx)) 20 | .Where(static m => m is not null)!; 21 | 22 | IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = 23 | context.CompilationProvider.Combine(classDeclarations.Collect()); 24 | 25 | context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Item1, source.Item2, spc)); 26 | } 27 | 28 | private static bool IsSettingsClass(SyntaxNode node) 29 | { 30 | return node is ClassDeclarationSyntax classDeclaration && classDeclaration.Identifier.Text.EndsWith("Settings"); 31 | } 32 | 33 | private static ClassDeclarationSyntax GetClassDeclaration(GeneratorSyntaxContext context) 34 | { 35 | return (ClassDeclarationSyntax)context.Node; 36 | } 37 | 38 | private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) 39 | { 40 | foreach (ClassDeclarationSyntax classDecl in classes) 41 | { 42 | SemanticModel semanticModel = compilation.GetSemanticModel(classDecl.SyntaxTree); 43 | INamedTypeSymbol classSymbol = semanticModel.GetDeclaredSymbol(classDecl)!; 44 | 45 | StringBuilder sourceBuilder = new(); 46 | 47 | sourceBuilder.AppendLine("#nullable enable"); 48 | sourceBuilder.AppendLine("using System;"); 49 | sourceBuilder.AppendLine("using System.Collections.Generic;"); 50 | sourceBuilder.AppendLine("using Newtonsoft.Json;"); 51 | 52 | sourceBuilder.AppendLine($"namespace {classSymbol.ContainingNamespace}"); 53 | sourceBuilder.AppendLine("{"); 54 | sourceBuilder.AppendLine($" internal partial class {classSymbol.Name}"); 55 | sourceBuilder.AppendLine(" {"); 56 | 57 | sourceBuilder.AppendLine(GetMethod(semanticModel, classDecl, 58 | "public void SetProperty(Queue propertyPath, string value)", 59 | propertyName => $"{propertyName}.SetProperty(propertyPath, value);", 60 | propertyName => $"{propertyName} = value;", 61 | includeBreak: true)); 62 | 63 | sourceBuilder.AppendLine(GetMethod(semanticModel, classDecl, 64 | "public object? GetProperty(Queue propertyPath)", 65 | propertyName => $"return {propertyName}.GetProperty(propertyPath);", 66 | propertyName => $"return {propertyName};", 67 | includeBreak: false)); 68 | 69 | sourceBuilder.AppendLine(" }"); // end class 70 | sourceBuilder.AppendLine("}"); // end namespace 71 | sourceBuilder.AppendLine("#nullable disable"); 72 | 73 | context.AddSource($"{classDecl.Identifier.Text}.Generated.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); 74 | } 75 | } 76 | 77 | private static string GetMethod( 78 | SemanticModel semanticModel, ClassDeclarationSyntax classDecl, string methodSignature, 79 | Func settingsPropertyAction, Func propertyAction, bool includeBreak) 80 | { 81 | StringBuilder sourceBuilder = new(); 82 | 83 | sourceBuilder.AppendLine($@" 84 | {methodSignature} 85 | {{ 86 | if (propertyPath.Count == 0) 87 | {{ 88 | throw new ArgumentException(""Property path cannot be empty"", nameof(propertyPath)); 89 | }} 90 | 91 | var currentProperty = propertyPath.Dequeue(); 92 | switch (currentProperty) 93 | {{"); 94 | 95 | foreach (PropertyDeclarationSyntax property in classDecl.DescendantNodes().OfType()) 96 | { 97 | AttributeSyntax jsonPropertyAttribute = property.AttributeLists 98 | .SelectMany(a => a.Attributes) 99 | .FirstOrDefault(a => a.Name.ToString() == "JsonProperty"); 100 | 101 | if (jsonPropertyAttribute != null) 102 | { 103 | AttributeArgumentSyntax attribArg = jsonPropertyAttribute.ArgumentList?.Arguments[0]!; 104 | string jsonPropertyName = semanticModel.GetConstantValue(attribArg.Expression).Value?.ToString().Trim('"')!; 105 | 106 | if (property.Type.ToString().EndsWith("Settings")) 107 | { 108 | sourceBuilder.AppendLine($@" 109 | case ""{jsonPropertyName}"": 110 | if (propertyPath.Count > 0) 111 | {{ 112 | {settingsPropertyAction(property.Identifier.Text)} 113 | }} 114 | else 115 | {{ 116 | throw new ArgumentException(""Property path must point to a valid property"", nameof(propertyPath)); 117 | }} 118 | {(includeBreak ? "break;" : string.Empty)}"); 119 | } 120 | else 121 | { 122 | sourceBuilder.AppendLine($@" 123 | case ""{jsonPropertyName}"": 124 | if (propertyPath.Count > 0) 125 | {{ 126 | throw new ArgumentException(""Property path must point to a valid property"", nameof(propertyPath)); 127 | }} 128 | {propertyAction(property.Identifier.Text)} 129 | {(includeBreak ? "break;" : string.Empty)}"); 130 | } 131 | } 132 | } 133 | 134 | sourceBuilder.AppendLine($@" 135 | default: 136 | throw new ArgumentException($""Unknown property: {{currentProperty}}"", nameof(propertyPath)); 137 | }} 138 | }}"); 139 | 140 | return sourceBuilder.ToString(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Analyzers/Valleysoft.Dredge.Analyzers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | enable 6 | enable 7 | true 8 | 12.0 9 | IDE0290 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/DockerfileCommandTests.cs: -------------------------------------------------------------------------------- 1 | namespace Valleysoft.Dredge.Tests; 2 | 3 | using Newtonsoft.Json; 4 | using Spectre.Console; 5 | using System.Text; 6 | using Valleysoft.DockerRegistryClient.Models.Images; 7 | using Valleysoft.DockerRegistryClient.Models.Manifests; 8 | using Valleysoft.DockerRegistryClient.Models.Manifests.Docker; 9 | using Valleysoft.Dredge.Commands.Image; 10 | 11 | public class DockerfileCommandTests 12 | { 13 | private const string Registry = "test-registry.io"; 14 | 15 | public static IEnumerable> GetTestData() 16 | { 17 | DirectoryInfo workingDir = new(Path.Combine(Environment.CurrentDirectory, "TestData", "DockerfileCommand")); 18 | return workingDir.GetDirectories() 19 | .SelectMany(dir => new TestScenario[] 20 | { 21 | new( 22 | dir.Name, 23 | Path.Combine(dir.FullName, "image.json"), 24 | noFormat: false, 25 | Path.Combine(dir.FullName, "expected-output-format.txt")), 26 | new( 27 | dir.Name, 28 | Path.Combine(dir.FullName, "image.json"), 29 | noFormat: true, 30 | Path.Combine(dir.FullName, "expected-output-no-format.txt")) 31 | }) 32 | .Select(scenario => new TheoryDataRow(scenario)); 33 | 34 | } 35 | 36 | public class TestScenario 37 | { 38 | public TestScenario(string name, string imagePath, bool noFormat, string expectedOutputPath) 39 | { 40 | Name = name; 41 | ImagePath = imagePath; 42 | NoFormat = noFormat; 43 | ExpectedOutputPath = expectedOutputPath; 44 | } 45 | 46 | public string Name { get; } 47 | public string ImagePath { get; } 48 | public bool NoFormat { get; } 49 | public string ExpectedOutputPath { get; } 50 | } 51 | 52 | 53 | [Theory] 54 | [MemberData(nameof(GetTestData))] 55 | public async Task Test(TestScenario scenario) 56 | { 57 | const string RepoName = "repo"; 58 | const string TagName = "tag"; 59 | const string ImageName = $"{Registry}/{RepoName}:{TagName}"; 60 | const string Digest = "digest"; 61 | 62 | Mock clientFactoryMock = new(); 63 | Mock mcrClientMock = new(); 64 | 65 | clientFactoryMock 66 | .Setup(o => o.GetClientAsync(RegistryHelper.McrRegistry)) 67 | .ReturnsAsync(mcrClientMock.Object); 68 | 69 | ManifestLayer[] layers = []; 70 | Image image = JsonConvert.DeserializeObject(File.ReadAllText(scenario.ImagePath))!; 71 | if (image.Os == "windows") 72 | { 73 | layers = 74 | [ 75 | new ManifestLayer 76 | { 77 | Digest = "layer0digest" 78 | }, 79 | new ManifestLayer 80 | { 81 | Digest = "layer1digest" 82 | } 83 | ]; 84 | 85 | mcrClientMock 86 | .Setup(o => o.Blobs.ExistsAsync(It.IsAny(), It.IsAny(), It.IsAny())) 87 | .ReturnsAsync(false); 88 | 89 | mcrClientMock 90 | .Setup(o => o.Blobs.ExistsAsync( 91 | "windows/servercore", It.Is(digest => digest == "layer0digest" || digest == "layer1digest"), It.IsAny())) 92 | .ReturnsAsync(true); 93 | } 94 | 95 | Mock registryClientMock = new(); 96 | registryClientMock 97 | .Setup(o => o.Manifests.GetAsync(RepoName, TagName, It.IsAny())) 98 | .ReturnsAsync(new ManifestInfo("media-type", "digest", 99 | new DockerManifest 100 | { 101 | Config = new ManifestConfig 102 | { 103 | Digest = Digest 104 | }, 105 | Layers = layers 106 | })); 107 | 108 | registryClientMock 109 | .Setup(o => o.Blobs.GetAsync(RepoName, Digest, It.IsAny())) 110 | .ReturnsAsync(new MemoryStream(Encoding.UTF8.GetBytes(File.ReadAllText(scenario.ImagePath)))); 111 | 112 | clientFactoryMock 113 | .Setup(o => o.GetClientAsync(Registry)) 114 | .ReturnsAsync(registryClientMock.Object); 115 | 116 | DockerfileCommand command = new(clientFactoryMock.Object) 117 | { 118 | Options = new DockerfileOptions 119 | { 120 | Image = ImageName, 121 | NoFormat = scenario.NoFormat 122 | } 123 | }; 124 | 125 | string markupStr = await command.GetMarkupStringAsync(); 126 | 127 | string actual = TestHelper.Normalize(markupStr); 128 | string expected = TestHelper.Normalize(File.ReadAllText(scenario.ExpectedOutputPath)); 129 | Assert.Equal(expected, actual); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/fedora/expected-output-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#6D9A58]# No instruction info 3 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/fedora/expected-output-no-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#6D9A58]# No instruction info 3 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/fedora/image.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "registry.fedoraproject.org/fedora@sha256:9b8e2b468d0d0523529bde44e8a0f17aad93dcedcf876c111183ffd4ea40d724", 3 | "os": "linux", 4 | "history": [ 5 | { 6 | "comment": "Created by Image Factory", 7 | "created": "2022-12-09T05:50:20Z" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/golang/expected-output-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#C285BF]ADD[/] [#96DCFE]file:c13b430c8699df107ffd9ea5230b92238bc037a8e1cbbe35d6ab664941d575da[/] [#96DCFE]/[/] 3 | [#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"bash"[/][#FAC81F]]][/] 4 | [#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 5 | apt-get update; [#FAC81F]\[/] 6 | apt-get install -y --no-install-recommends ca-certificates curl netbase wget ; [#FAC81F]\[/] 7 | rm -rf /var/lib/apt/lists/* 8 | [/][#C285BF]RUN[/] [#96DCFE]set -ex; [#FAC81F]\[/] 9 | if ! command -v gpg > /dev/null; then apt-get update; [#FAC81F]\[/] 10 | apt-get install -y --no-install-recommends gnupg dirmngr ; [#FAC81F]\[/] 11 | rm -rf /var/lib/apt/lists/*; [#FAC81F]\[/] 12 | fi 13 | [/][#C285BF]RUN[/] [#96DCFE]apt-get update && apt-get install -y --no-install-recommends git mercurial openssh-client subversion procps [#FAC81F]\[/] 14 | && rm -rf /var/lib/apt/lists/* 15 | [/][#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 16 | apt-get update; [#FAC81F]\[/] 17 | apt-get install -y --no-install-recommends g++ gcc libc6-dev make pkg-config ; [#FAC81F]\[/] 18 | rm -rf /var/lib/apt/lists/* 19 | [/][#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin[/] 20 | [#C285BF]ENV[/] [green]GOLANG_VERSION[/][#FAC81F]=[/][#96DCFE]1.19.4[/] 21 | [#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 22 | arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; [#FAC81F]\[/] 23 | url=; [#FAC81F]\[/] 24 | case "$arch" in 'amd64') url='https://dl.google.com/go/go1.19.4.linux-amd64.tar.gz'; [#FAC81F]\[/] 25 | sha256='c9c08f783325c4cf840a94333159cc937f05f75d36a8b307951d5bd959cf2ab8'; [#FAC81F]\[/] 26 | ;; [#FAC81F]\[/] 27 | 'armel') export GOARCH='arm' GOARM='5' GOOS='linux'; [#FAC81F]\[/] 28 | ;; [#FAC81F]\[/] 29 | 'armhf') url='https://dl.google.com/go/go1.19.4.linux-armv6l.tar.gz'; [#FAC81F]\[/] 30 | sha256='7a51dae4f3a52d2dfeaf59367cc0b8a296deddc87e95aa619bf87d24661d2370'; [#FAC81F]\[/] 31 | ;; [#FAC81F]\[/] 32 | 'arm64') url='https://dl.google.com/go/go1.19.4.linux-arm64.tar.gz'; [#FAC81F]\[/] 33 | sha256='9df122d6baf6f2275270306b92af3b09d7973fb1259257e284dba33c0db14f1b'; [#FAC81F]\[/] 34 | ;; [#FAC81F]\[/] 35 | 'i386') url='https://dl.google.com/go/go1.19.4.linux-386.tar.gz'; [#FAC81F]\[/] 36 | sha256='e5f0b0551e120bf3d1246cb960ec58032d7ca69e1adcf0fdb91c07da620e0c61'; [#FAC81F]\[/] 37 | ;; [#FAC81F]\[/] 38 | 'mips64el') export GOARCH='mips64le' GOOS='linux'; [#FAC81F]\[/] 39 | ;; [#FAC81F]\[/] 40 | 'ppc64el') url='https://dl.google.com/go/go1.19.4.linux-ppc64le.tar.gz'; [#FAC81F]\[/] 41 | sha256='fbc6c7d1d169bbdc82223d861d2fadc6add01c126533d3efbba3fdca9b362035'; [#FAC81F]\[/] 42 | ;; [#FAC81F]\[/] 43 | 's390x') url='https://dl.google.com/go/go1.19.4.linux-s390x.tar.gz'; [#FAC81F]\[/] 44 | sha256='4b8d25acbdca8010c31ea8c5fd4aba93471ff6ada7a8b4fb04b935baee873dc8'; [#FAC81F]\[/] 45 | ;; [#FAC81F]\[/] 46 | *) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; [#FAC81F]\[/] 47 | esac; [#FAC81F]\[/] 48 | build=; [#FAC81F]\[/] 49 | if [[ -z "$url" ]]; then build=1; [#FAC81F]\[/] 50 | url='https://dl.google.com/go/go1.19.4.src.tar.gz'; [#FAC81F]\[/] 51 | sha256='eda74db4ac494800a3e66ee784e495bfbb9b8e535df924a8b01b1a8028b7f368'; [#FAC81F]\[/] 52 | echo >&2; [#FAC81F]\[/] 53 | echo >&2 "warning: current architecture ($arch) does not have a compatible Go binary release; will be building from source"; [#FAC81F]\[/] 54 | echo >&2; [#FAC81F]\[/] 55 | fi; [#FAC81F]\[/] 56 | wget -O go.tgz.asc "$url.asc"; [#FAC81F]\[/] 57 | wget -O go.tgz "$url" --progress=dot:giga; [#FAC81F]\[/] 58 | echo "$sha256 *go.tgz" | sha256sum -c -; [#FAC81F]\[/] 59 | GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; [#FAC81F]\[/] 60 | gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 'EB4C 1BFD 4F04 2F6D DDCC EC91 7721 F63B D38B 4796'; [#FAC81F]\[/] 61 | gpg --batch --keyserver keyserver.ubuntu.com --recv-keys '2F52 8D36 D67B 69ED F998 D857 78BD 6547 3CB3 BD13'; [#FAC81F]\[/] 62 | gpg --batch --verify go.tgz.asc go.tgz; [#FAC81F]\[/] 63 | gpgconf --kill all; [#FAC81F]\[/] 64 | rm -rf "$GNUPGHOME" go.tgz.asc; [#FAC81F]\[/] 65 | tar -C /usr/local -xzf go.tgz; [#FAC81F]\[/] 66 | rm go.tgz; [#FAC81F]\[/] 67 | if [[ -n "$build" ]]; then savedAptMark="$(apt-mark showmanual)"; [#FAC81F]\[/] 68 | apt-get update; [#FAC81F]\[/] 69 | apt-get install -y --no-install-recommends golang-go; [#FAC81F]\[/] 70 | export GOCACHE='/tmp/gocache'; [#FAC81F]\[/] 71 | ( cd /usr/local/go/src; [#FAC81F]\[/] 72 | export GOROOT_BOOTSTRAP="$(go env GOROOT)" GOHOSTOS="$GOOS" GOHOSTARCH="$GOARCH"; [#FAC81F]\[/] 73 | ./make.bash; [#FAC81F]\[/] 74 | ); [#FAC81F]\[/] 75 | apt-mark auto '.*' > /dev/null; [#FAC81F]\[/] 76 | apt-mark manual $savedAptMark > /dev/null; [#FAC81F]\[/] 77 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; [#FAC81F]\[/] 78 | rm -rf /var/lib/apt/lists/*; [#FAC81F]\[/] 79 | rm -rf /usr/local/go/pkg/*/cmd /usr/local/go/pkg/bootstrap /usr/local/go/pkg/obj /usr/local/go/pkg/tool/*/api /usr/local/go/pkg/tool/*/go_bootstrap /usr/local/go/src/cmd/dist/dist "$GOCACHE" ; [#FAC81F]\[/] 80 | fi; [#FAC81F]\[/] 81 | go version 82 | [/][#C285BF]ENV[/] [green]GOPATH[/][#FAC81F]=[/][#96DCFE]/go[/] 83 | [#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin[/] 84 | [#C285BF]RUN[/] [#96DCFE]mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH" 85 | [/][#C285BF]WORKDIR[/] [#96DCFE]/go 86 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/golang/expected-output-no-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#C285BF]ADD[/] [#96DCFE]file:c13b430c8699df107ffd9ea5230b92238bc037a8e1cbbe35d6ab664941d575da[/] [#96DCFE]/[/] 3 | [#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"bash"[/][#FAC81F]]][/] 4 | [#C285BF]RUN[/] [#96DCFE]set -eux; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl netbase wget ; rm -rf /var/lib/apt/lists/* 5 | [/][#C285BF]RUN[/] [#96DCFE]set -ex; if ! command -v gpg > /dev/null; then apt-get update; apt-get install -y --no-install-recommends gnupg dirmngr ; rm -rf /var/lib/apt/lists/*; fi 6 | [/][#C285BF]RUN[/] [#96DCFE]apt-get update && apt-get install -y --no-install-recommends git mercurial openssh-client subversion procps && rm -rf /var/lib/apt/lists/* 7 | [/][#C285BF]RUN[/] [#96DCFE]set -eux; apt-get update; apt-get install -y --no-install-recommends g++ gcc libc6-dev make pkg-config ; rm -rf /var/lib/apt/lists/* 8 | [/][#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin[/] 9 | [#C285BF]ENV[/] [green]GOLANG_VERSION[/][#FAC81F]=[/][#96DCFE]1.19.4[/] 10 | [#C285BF]RUN[/] [#96DCFE]set -eux; arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; url=; case "$arch" in 'amd64') url='https://dl.google.com/go/go1.19.4.linux-amd64.tar.gz'; sha256='c9c08f783325c4cf840a94333159cc937f05f75d36a8b307951d5bd959cf2ab8'; ;; 'armel') export GOARCH='arm' GOARM='5' GOOS='linux'; ;; 'armhf') url='https://dl.google.com/go/go1.19.4.linux-armv6l.tar.gz'; sha256='7a51dae4f3a52d2dfeaf59367cc0b8a296deddc87e95aa619bf87d24661d2370'; ;; 'arm64') url='https://dl.google.com/go/go1.19.4.linux-arm64.tar.gz'; sha256='9df122d6baf6f2275270306b92af3b09d7973fb1259257e284dba33c0db14f1b'; ;; 'i386') url='https://dl.google.com/go/go1.19.4.linux-386.tar.gz'; sha256='e5f0b0551e120bf3d1246cb960ec58032d7ca69e1adcf0fdb91c07da620e0c61'; ;; 'mips64el') export GOARCH='mips64le' GOOS='linux'; ;; 'ppc64el') url='https://dl.google.com/go/go1.19.4.linux-ppc64le.tar.gz'; sha256='fbc6c7d1d169bbdc82223d861d2fadc6add01c126533d3efbba3fdca9b362035'; ;; 's390x') url='https://dl.google.com/go/go1.19.4.linux-s390x.tar.gz'; sha256='4b8d25acbdca8010c31ea8c5fd4aba93471ff6ada7a8b4fb04b935baee873dc8'; ;; *) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; esac; build=; if [[ -z "$url" ]]; then build=1; url='https://dl.google.com/go/go1.19.4.src.tar.gz'; sha256='eda74db4ac494800a3e66ee784e495bfbb9b8e535df924a8b01b1a8028b7f368'; echo >&2; echo >&2 "warning: current architecture ($arch) does not have a compatible Go binary release; will be building from source"; echo >&2; fi; wget -O go.tgz.asc "$url.asc"; wget -O go.tgz "$url" --progress=dot:giga; echo "$sha256 *go.tgz" | sha256sum -c -; GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 'EB4C 1BFD 4F04 2F6D DDCC EC91 7721 F63B D38B 4796'; gpg --batch --keyserver keyserver.ubuntu.com --recv-keys '2F52 8D36 D67B 69ED F998 D857 78BD 6547 3CB3 BD13'; gpg --batch --verify go.tgz.asc go.tgz; gpgconf --kill all; rm -rf "$GNUPGHOME" go.tgz.asc; tar -C /usr/local -xzf go.tgz; rm go.tgz; if [[ -n "$build" ]]; then savedAptMark="$(apt-mark showmanual)"; apt-get update; apt-get install -y --no-install-recommends golang-go; export GOCACHE='/tmp/gocache'; ( cd /usr/local/go/src; export GOROOT_BOOTSTRAP="$(go env GOROOT)" GOHOSTOS="$GOOS" GOHOSTARCH="$GOARCH"; ./make.bash; ); apt-mark auto '.*' > /dev/null; apt-mark manual $savedAptMark > /dev/null; apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; rm -rf /var/lib/apt/lists/*; rm -rf /usr/local/go/pkg/*/cmd /usr/local/go/pkg/bootstrap /usr/local/go/pkg/obj /usr/local/go/pkg/tool/*/api /usr/local/go/pkg/tool/*/go_bootstrap /usr/local/go/src/cmd/dist/dist "$GOCACHE" ; fi; go version 11 | [/][#C285BF]ENV[/] [green]GOPATH[/][#FAC81F]=[/][#96DCFE]/go[/] 12 | [#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin[/] 13 | [#C285BF]RUN[/] [#96DCFE]mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH" 14 | [/][#C285BF]WORKDIR[/] [#96DCFE]/go 15 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/golang/image.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "library/golang@sha256:766625f2182dacec4c8774355a65a81a3b73acb0b4287b6a32a8efc185aede2c", 3 | "os": "linux", 4 | "history": [ 5 | { 6 | "created": "2022-12-21T01:20:21.922936512Z", 7 | "created_by": "/bin/sh -c #(nop) ADD file:c13b430c8699df107ffd9ea5230b92238bc037a8e1cbbe35d6ab664941d575da in / " 8 | }, 9 | { 10 | "created": "2022-12-21T01:20:22.590344295Z", 11 | "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", 12 | "empty_layer": true 13 | }, 14 | { 15 | "created": "2022-12-21T11:13:51.512187477Z", 16 | "created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\tcurl \t\tnetbase \t\twget \t; \trm -rf /var/lib/apt/lists/*" 17 | }, 18 | { 19 | "created": "2022-12-21T11:13:58.806979115Z", 20 | "created_by": "/bin/sh -c set -ex; \tif ! command -v gpg > /dev/null; then \t\tapt-get update; \t\tapt-get install -y --no-install-recommends \t\t\tgnupg \t\t\tdirmngr \t\t; \t\trm -rf /var/lib/apt/lists/*; \tfi" 21 | }, 22 | { 23 | "created": "2022-12-21T11:14:17.676013622Z", 24 | "created_by": "/bin/sh -c apt-get update && apt-get install -y --no-install-recommends \t\tgit \t\tmercurial \t\topenssh-client \t\tsubversion \t\t\t\tprocps \t&& rm -rf /var/lib/apt/lists/*" 25 | }, 26 | { 27 | "created": "2022-12-21T17:03:24.401848277Z", 28 | "created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tg++ \t\tgcc \t\tlibc6-dev \t\tmake \t\tpkg-config \t; \trm -rf /var/lib/apt/lists/*" 29 | }, 30 | { 31 | "created": "2022-12-21T17:03:25.042977729Z", 32 | "created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 33 | "empty_layer": true 34 | }, 35 | { 36 | "created": "2022-12-21T17:04:20.804087865Z", 37 | "created_by": "/bin/sh -c #(nop) ENV GOLANG_VERSION=1.19.4", 38 | "empty_layer": true 39 | }, 40 | { 41 | "created": "2022-12-21T17:04:34.285435675Z", 42 | "created_by": "/bin/sh -c set -eux; \tarch=\"$(dpkg --print-architecture)\"; arch=\"${arch##*-}\"; \turl=; \tcase \"$arch\" in \t\t'amd64') \t\t\turl='https://dl.google.com/go/go1.19.4.linux-amd64.tar.gz'; \t\t\tsha256='c9c08f783325c4cf840a94333159cc937f05f75d36a8b307951d5bd959cf2ab8'; \t\t\t;; \t\t'armel') \t\t\texport GOARCH='arm' GOARM='5' GOOS='linux'; \t\t\t;; \t\t'armhf') \t\t\turl='https://dl.google.com/go/go1.19.4.linux-armv6l.tar.gz'; \t\t\tsha256='7a51dae4f3a52d2dfeaf59367cc0b8a296deddc87e95aa619bf87d24661d2370'; \t\t\t;; \t\t'arm64') \t\t\turl='https://dl.google.com/go/go1.19.4.linux-arm64.tar.gz'; \t\t\tsha256='9df122d6baf6f2275270306b92af3b09d7973fb1259257e284dba33c0db14f1b'; \t\t\t;; \t\t'i386') \t\t\turl='https://dl.google.com/go/go1.19.4.linux-386.tar.gz'; \t\t\tsha256='e5f0b0551e120bf3d1246cb960ec58032d7ca69e1adcf0fdb91c07da620e0c61'; \t\t\t;; \t\t'mips64el') \t\t\texport GOARCH='mips64le' GOOS='linux'; \t\t\t;; \t\t'ppc64el') \t\t\turl='https://dl.google.com/go/go1.19.4.linux-ppc64le.tar.gz'; \t\t\tsha256='fbc6c7d1d169bbdc82223d861d2fadc6add01c126533d3efbba3fdca9b362035'; \t\t\t;; \t\t's390x') \t\t\turl='https://dl.google.com/go/go1.19.4.linux-s390x.tar.gz'; \t\t\tsha256='4b8d25acbdca8010c31ea8c5fd4aba93471ff6ada7a8b4fb04b935baee873dc8'; \t\t\t;; \t\t*) echo >&2 \"error: unsupported architecture '$arch' (likely packaging update needed)\"; exit 1 ;; \tesac; \tbuild=; \tif [ -z \"$url\" ]; then \t\tbuild=1; \t\turl='https://dl.google.com/go/go1.19.4.src.tar.gz'; \t\tsha256='eda74db4ac494800a3e66ee784e495bfbb9b8e535df924a8b01b1a8028b7f368'; \t\techo >&2; \t\techo >&2 \"warning: current architecture ($arch) does not have a compatible Go binary release; will be building from source\"; \t\techo >&2; \tfi; \t\twget -O go.tgz.asc \"$url.asc\"; \twget -O go.tgz \"$url\" --progress=dot:giga; \techo \"$sha256 *go.tgz\" | sha256sum -c -; \t\tGNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME; \tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys 'EB4C 1BFD 4F04 2F6D DDCC EC91 7721 F63B D38B 4796'; \tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys '2F52 8D36 D67B 69ED F998 D857 78BD 6547 3CB3 BD13'; \tgpg --batch --verify go.tgz.asc go.tgz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" go.tgz.asc; \t\ttar -C /usr/local -xzf go.tgz; \trm go.tgz; \t\tif [ -n \"$build\" ]; then \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \t\tapt-get update; \t\tapt-get install -y --no-install-recommends golang-go; \t\t\t\texport GOCACHE='/tmp/gocache'; \t\t\t\t( \t\t\tcd /usr/local/go/src; \t\t\texport GOROOT_BOOTSTRAP=\"$(go env GOROOT)\" GOHOSTOS=\"$GOOS\" GOHOSTARCH=\"$GOARCH\"; \t\t\t./make.bash; \t\t); \t\t\t\tapt-mark auto '.*' > /dev/null; \t\tapt-mark manual $savedAptMark > /dev/null; \t\tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\trm -rf /var/lib/apt/lists/*; \t\t\t\trm -rf \t\t\t/usr/local/go/pkg/*/cmd \t\t\t/usr/local/go/pkg/bootstrap \t\t\t/usr/local/go/pkg/obj \t\t\t/usr/local/go/pkg/tool/*/api \t\t\t/usr/local/go/pkg/tool/*/go_bootstrap \t\t\t/usr/local/go/src/cmd/dist/dist \t\t\t\"$GOCACHE\" \t\t; \tfi; \t\tgo version" 43 | }, 44 | { 45 | "created": "2022-12-21T17:04:35.657671507Z", 46 | "created_by": "/bin/sh -c #(nop) ENV GOPATH=/go", 47 | "empty_layer": true 48 | }, 49 | { 50 | "created": "2022-12-21T17:04:35.763897771Z", 51 | "created_by": "/bin/sh -c #(nop) ENV PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 52 | "empty_layer": true 53 | }, 54 | { 55 | "created": "2022-12-21T17:04:36.338119752Z", 56 | "created_by": "/bin/sh -c mkdir -p \"$GOPATH/src\" \"$GOPATH/bin\" && chmod -R 777 \"$GOPATH\"" 57 | }, 58 | { 59 | "created": "2022-12-21T17:04:36.454187246Z", 60 | "created_by": "/bin/sh -c #(nop) WORKDIR /go", 61 | "empty_layer": true 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/mariner/expected-output-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#6D9A58]# No instruction info 3 | [/][#C285BF]ARG[/] [green]EULA[/][#FAC81F]=[/][#96DCFE]@EULA_FILE@[/] 4 | [#C285BF]COPY[/] [#96DCFE]EULA-Container.txt[/] [#96DCFE].[/] [#6D9A58]# buildkit 5 | [/][#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"bash"[/][#FAC81F]]][/] 6 | [#C285BF]ENV[/] [green]ASPNETCORE_URLS[/][#FAC81F]=[/][#96DCFE]http://+:80[/] [#FAC81F]\[/] 7 | [green]DOTNET_RUNNING_IN_CONTAINER[/][#FAC81F]=[/][#96DCFE]true[/] 8 | [#C285BF]RUN[/] [#96DCFE]/bin/sh -c tdnf install -y ca-certificates glibc icu krb5 libgcc libstdc++ openssl-libs zlib && tdnf clean all # buildkit 9 | [/][#C285BF]RUN[/] [#96DCFE]/bin/sh -c dotnet_version=6.0.36 && curl -fSL --output dotnet-runtime-deps.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$dotnet_version/dotnet-runtime-deps-$dotnet_version-cm.2-x64.rpm && dotnet_sha512='c480ab7722f34eef1e0e55bf60b393cf6f4dc51ea78878b5a3b0e9c8c45d054d3c81b29a8783732c01ca62d3c5b6298a166a2de7c0e07f70067f22af375b6f1c' && echo "$dotnet_sha512 dotnet-runtime-deps.rpm" | sha512sum -c - && tdnf install -y --disablerepo=* dotnet-runtime-deps.rpm && rm dotnet-runtime-deps.rpm # buildkit 10 | [/][#C285BF]ENV[/] [green]DOTNET_VERSION[/][#FAC81F]=[/][#96DCFE]6.0.36[/] 11 | [#C285BF]RUN[/] [#96DCFE]/bin/sh -c curl -fSL --output dotnet-host.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-host-$DOTNET_VERSION-x64.rpm && dotnet_sha512='00d9f978054aef3ec4b2bd81eedee8184e4fd44eee364d82b81eab7079958a9d0fe6cfdf5f29c05f33a20e2cfc9761306a8bcc378dbc804cf1f38eb9ea29871e' && echo "$dotnet_sha512 dotnet-host.rpm" | sha512sum -c - && curl -fSL --output dotnet-hostfxr.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-hostfxr-$DOTNET_VERSION-x64.rpm && dotnet_sha512='79faa94cef34307a1d947300755e002056f42094003b3fc3447efea6731ddece3d6d18ab62c0292498418a627ba395c6c2053a8b92fbdac356cd0afb141e7e7d' && echo "$dotnet_sha512 dotnet-hostfxr.rpm" | sha512sum -c - && curl -fSL --output dotnet-runtime.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-x64.rpm && dotnet_sha512='a3a544b6d315daa8e4fceb75d7414502d5b8fa5c6f7dc14c5ea05a8c32d50adf8422471eac69893eb8ea10ff908879aea277fc2b6aa5a723b3f60cf3c2e84c7e' && echo "$dotnet_sha512 dotnet-runtime.rpm" | sha512sum -c - && tdnf install -y --disablerepo=* dotnet-host.rpm dotnet-hostfxr.rpm dotnet-runtime.rpm && rm dotnet-host.rpm dotnet-hostfxr.rpm dotnet-runtime.rpm # buildkit 12 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/mariner/expected-output-no-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#6D9A58]# No instruction info 3 | [/][#C285BF]ARG[/] [green]EULA[/][#FAC81F]=[/][#96DCFE]@EULA_FILE@[/] 4 | [#C285BF]COPY[/] [#96DCFE]EULA-Container.txt[/] [#96DCFE].[/] [#6D9A58]# buildkit 5 | [/][#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"bash"[/][#FAC81F]]][/] 6 | [#C285BF]ENV[/] [green]ASPNETCORE_URLS[/][#FAC81F]=[/][#96DCFE]http://+:80[/] [green]DOTNET_RUNNING_IN_CONTAINER[/][#FAC81F]=[/][#96DCFE]true[/] 7 | [#C285BF]RUN[/] [#96DCFE]/bin/sh -c tdnf install -y ca-certificates glibc icu krb5 libgcc libstdc++ openssl-libs zlib && tdnf clean all # buildkit 8 | [/][#C285BF]RUN[/] [#96DCFE]/bin/sh -c dotnet_version=6.0.36 && curl -fSL --output dotnet-runtime-deps.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$dotnet_version/dotnet-runtime-deps-$dotnet_version-cm.2-x64.rpm && dotnet_sha512='c480ab7722f34eef1e0e55bf60b393cf6f4dc51ea78878b5a3b0e9c8c45d054d3c81b29a8783732c01ca62d3c5b6298a166a2de7c0e07f70067f22af375b6f1c' && echo "$dotnet_sha512 dotnet-runtime-deps.rpm" | sha512sum -c - && tdnf install -y --disablerepo=* dotnet-runtime-deps.rpm && rm dotnet-runtime-deps.rpm # buildkit 9 | [/][#C285BF]ENV[/] [green]DOTNET_VERSION[/][#FAC81F]=[/][#96DCFE]6.0.36[/] 10 | [#C285BF]RUN[/] [#96DCFE]/bin/sh -c curl -fSL --output dotnet-host.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-host-$DOTNET_VERSION-x64.rpm && dotnet_sha512='00d9f978054aef3ec4b2bd81eedee8184e4fd44eee364d82b81eab7079958a9d0fe6cfdf5f29c05f33a20e2cfc9761306a8bcc378dbc804cf1f38eb9ea29871e' && echo "$dotnet_sha512 dotnet-host.rpm" | sha512sum -c - && curl -fSL --output dotnet-hostfxr.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-hostfxr-$DOTNET_VERSION-x64.rpm && dotnet_sha512='79faa94cef34307a1d947300755e002056f42094003b3fc3447efea6731ddece3d6d18ab62c0292498418a627ba395c6c2053a8b92fbdac356cd0afb141e7e7d' && echo "$dotnet_sha512 dotnet-hostfxr.rpm" | sha512sum -c - && curl -fSL --output dotnet-runtime.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-x64.rpm && dotnet_sha512='a3a544b6d315daa8e4fceb75d7414502d5b8fa5c6f7dc14c5ea05a8c32d50adf8422471eac69893eb8ea10ff908879aea277fc2b6aa5a723b3f60cf3c2e84c7e' && echo "$dotnet_sha512 dotnet-runtime.rpm" | sha512sum -c - && tdnf install -y --disablerepo=* dotnet-host.rpm dotnet-hostfxr.rpm dotnet-runtime.rpm && rm dotnet-host.rpm dotnet-hostfxr.rpm dotnet-runtime.rpm # buildkit 11 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/mariner/image.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "mcr.microsoft.com/dotnet/runtime@sha256:fc41073691b0e33a21de5ed4fd64f06bbc827031e975e7b317e0a4ced85a96ed", 3 | "os": "linux", 4 | "history": [ 5 | { 6 | "created": "2024-12-08T04:27:33.807635718Z", 7 | "comment": "Imported from -" 8 | }, 9 | { 10 | "created": "2024-12-08T04:27:42.708534541Z", 11 | "created_by": "ARG EULA=@EULA_FILE@", 12 | "comment": "buildkit.dockerfile.v0", 13 | "empty_layer": true 14 | }, 15 | { 16 | "created": "2024-12-08T04:27:42.708534541Z", 17 | "created_by": "COPY EULA-Container.txt . # buildkit", 18 | "comment": "buildkit.dockerfile.v0" 19 | }, 20 | { 21 | "created": "2024-12-08T04:27:42.708534541Z", 22 | "created_by": "CMD [\"bash\"]", 23 | "comment": "buildkit.dockerfile.v0", 24 | "empty_layer": true 25 | }, 26 | { 27 | "created": "2024-12-11T20:31:20.806995463Z", 28 | "created_by": "ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true", 29 | "comment": "buildkit.dockerfile.v0", 30 | "empty_layer": true 31 | }, 32 | { 33 | "created": "2024-12-11T20:31:20.806995463Z", 34 | "created_by": "RUN /bin/sh -c tdnf install -y ca-certificates glibc icu krb5 libgcc libstdc++ openssl-libs zlib \u0026\u0026 tdnf clean all # buildkit", 35 | "comment": "buildkit.dockerfile.v0" 36 | }, 37 | { 38 | "created": "2024-12-11T20:31:22.638464065Z", 39 | "created_by": "RUN /bin/sh -c dotnet_version=6.0.36 \u0026\u0026 curl -fSL --output dotnet-runtime-deps.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$dotnet_version/dotnet-runtime-deps-$dotnet_version-cm.2-x64.rpm \u0026\u0026 dotnet_sha512='c480ab7722f34eef1e0e55bf60b393cf6f4dc51ea78878b5a3b0e9c8c45d054d3c81b29a8783732c01ca62d3c5b6298a166a2de7c0e07f70067f22af375b6f1c' \u0026\u0026 echo \"$dotnet_sha512 dotnet-runtime-deps.rpm\" | sha512sum -c - \u0026\u0026 tdnf install -y --disablerepo=* dotnet-runtime-deps.rpm \u0026\u0026 rm dotnet-runtime-deps.rpm # buildkit", 40 | "comment": "buildkit.dockerfile.v0" 41 | }, 42 | { 43 | "created": "2024-12-11T20:31:29.473616568Z", 44 | "created_by": "ENV DOTNET_VERSION=6.0.36", 45 | "comment": "buildkit.dockerfile.v0", 46 | "empty_layer": true 47 | }, 48 | { 49 | "created": "2024-12-11T20:31:29.473616568Z", 50 | "created_by": "RUN /bin/sh -c curl -fSL --output dotnet-host.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-host-$DOTNET_VERSION-x64.rpm \u0026\u0026 dotnet_sha512='00d9f978054aef3ec4b2bd81eedee8184e4fd44eee364d82b81eab7079958a9d0fe6cfdf5f29c05f33a20e2cfc9761306a8bcc378dbc804cf1f38eb9ea29871e' \u0026\u0026 echo \"$dotnet_sha512 dotnet-host.rpm\" | sha512sum -c - \u0026\u0026 curl -fSL --output dotnet-hostfxr.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-hostfxr-$DOTNET_VERSION-x64.rpm \u0026\u0026 dotnet_sha512='79faa94cef34307a1d947300755e002056f42094003b3fc3447efea6731ddece3d6d18ab62c0292498418a627ba395c6c2053a8b92fbdac356cd0afb141e7e7d' \u0026\u0026 echo \"$dotnet_sha512 dotnet-hostfxr.rpm\" | sha512sum -c - \u0026\u0026 curl -fSL --output dotnet-runtime.rpm https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-x64.rpm \u0026\u0026 dotnet_sha512='a3a544b6d315daa8e4fceb75d7414502d5b8fa5c6f7dc14c5ea05a8c32d50adf8422471eac69893eb8ea10ff908879aea277fc2b6aa5a723b3f60cf3c2e84c7e' \u0026\u0026 echo \"$dotnet_sha512 dotnet-runtime.rpm\" | sha512sum -c - \u0026\u0026 tdnf install -y --disablerepo=* dotnet-host.rpm dotnet-hostfxr.rpm dotnet-runtime.rpm \u0026\u0026 rm dotnet-host.rpm dotnet-hostfxr.rpm dotnet-runtime.rpm # buildkit", 51 | "comment": "buildkit.dockerfile.v0" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/openjdk/expected-output-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#6D9A58]# No instruction info 3 | [/][#C285BF]ARG[/] [green]EULA[/][#FAC81F]=[/][#96DCFE]@EULA_FILE@[/] 4 | [#C285BF]COPY[/] [#96DCFE]file:2c9b9395238ee55ff215d908ee6cf02975b45c4e6c97b276333bcd60ee705729[/] [#96DCFE].[/] 5 | [#C285BF]LABEL[/] [#96DCFE]Author[/][#FAC81F]=[/][#96DCFE]Microsoft[/] 6 | [#C285BF]LABEL[/] [#96DCFE]Support[/][#FAC81F]=[/][#CA9178]"Microsoft OpenJDK Support "[/] 7 | [#C285BF]COPY[/] [#96DCFE]/staging/[/] [#96DCFE]/[/] [#6D9A58]# buildkit 8 | [/][#C285BF]COPY[/] [#96DCFE]/usr/jdk/[/] [#96DCFE]/usr/jdk/[/] [#6D9A58]# buildkit 9 | [/][#C285BF]COPY[/] [#96DCFE]/staging/home/app[/] [#96DCFE]/home/app[/] [#6D9A58]# buildkit 10 | [/][#C285BF]ENV[/] [green]JAVA_HOME[/][#FAC81F]=[/][#96DCFE]/usr/jdk[/] 11 | [#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/jdk/bin[/] 12 | [#C285BF]ENTRYPOINT[/] [#FAC81F][[[/][#CA9178]"/usr/jdk/bin/java"[/][#FAC81F]]][/] 13 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/openjdk/expected-output-no-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#6D9A58]# No instruction info 3 | [/][#C285BF]ARG[/] [green]EULA[/][#FAC81F]=[/][#96DCFE]@EULA_FILE@[/] 4 | [#C285BF]COPY[/] [#96DCFE]file:2c9b9395238ee55ff215d908ee6cf02975b45c4e6c97b276333bcd60ee705729[/] [#96DCFE].[/] 5 | [#C285BF]LABEL[/] [#96DCFE]Author[/][#FAC81F]=[/][#96DCFE]Microsoft[/] 6 | [#C285BF]LABEL[/] [#96DCFE]Support[/][#FAC81F]=[/][#CA9178]"Microsoft OpenJDK Support "[/] 7 | [#C285BF]COPY[/] [#96DCFE]/staging/[/] [#96DCFE]/[/] [#6D9A58]# buildkit 8 | [/][#C285BF]COPY[/] [#96DCFE]/usr/jdk/[/] [#96DCFE]/usr/jdk/[/] [#6D9A58]# buildkit 9 | [/][#C285BF]COPY[/] [#96DCFE]/staging/home/app[/] [#96DCFE]/home/app[/] [#6D9A58]# buildkit 10 | [/][#C285BF]ENV[/] [green]JAVA_HOME[/][#FAC81F]=[/][#96DCFE]/usr/jdk[/] 11 | [#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/jdk/bin[/] 12 | [#C285BF]ENTRYPOINT[/] [#FAC81F][[[/][#CA9178]"/usr/jdk/bin/java"[/][#FAC81F]]][/] 13 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/openjdk/image.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "mcr.microsoft.com/openjdk/jdk@sha256:4b13d577acde77b9fc30ec9831b1e01751d3918fe2bbc5d13d047fdc81fa2957", 3 | "os": "linux", 4 | "history": [ 5 | { 6 | "created": "2023-04-16T23:55:54.508257077Z", 7 | "comment": "Imported from -" 8 | }, 9 | { 10 | "created": "2023-04-16T23:55:55.744677521Z", 11 | "created_by": "/bin/sh -c #(nop) ARG EULA=@EULA_FILE@", 12 | "empty_layer": true 13 | }, 14 | { 15 | "created": "2023-04-16T23:55:57.00065357Z", 16 | "created_by": "/bin/sh -c #(nop) COPY file:2c9b9395238ee55ff215d908ee6cf02975b45c4e6c97b276333bcd60ee705729 in . " 17 | }, 18 | { 19 | "created": "2023-04-24T09:11:59.635096955Z", 20 | "created_by": "LABEL Author=Microsoft", 21 | "comment": "buildkit.dockerfile.v0", 22 | "empty_layer": true 23 | }, 24 | { 25 | "created": "2023-04-24T09:11:59.635096955Z", 26 | "created_by": "LABEL Support=Microsoft OpenJDK Support ", 27 | "comment": "buildkit.dockerfile.v0", 28 | "empty_layer": true 29 | }, 30 | { 31 | "created": "2023-04-24T09:11:59.635096955Z", 32 | "created_by": "COPY /staging/ / # buildkit", 33 | "comment": "buildkit.dockerfile.v0" 34 | }, 35 | { 36 | "created": "2023-04-24T09:12:02.030218924Z", 37 | "created_by": "COPY /usr/jdk/ /usr/jdk/ # buildkit", 38 | "comment": "buildkit.dockerfile.v0" 39 | }, 40 | { 41 | "created": "2023-04-24T09:12:02.044834481Z", 42 | "created_by": "COPY /staging/home/app /home/app # buildkit", 43 | "comment": "buildkit.dockerfile.v0" 44 | }, 45 | { 46 | "created": "2023-04-24T09:12:02.044834481Z", 47 | "created_by": "ENV JAVA_HOME=/usr/jdk", 48 | "comment": "buildkit.dockerfile.v0", 49 | "empty_layer": true 50 | }, 51 | { 52 | "created": "2023-04-24T09:12:02.044834481Z", 53 | "created_by": "ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/jdk/bin", 54 | "comment": "buildkit.dockerfile.v0", 55 | "empty_layer": true 56 | }, 57 | { 58 | "created": "2023-04-24T09:12:02.044834481Z", 59 | "created_by": "ENTRYPOINT [\"/usr/jdk/bin/java\"]", 60 | "comment": "buildkit.dockerfile.v0", 61 | "empty_layer": true 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/python/expected-output-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#C285BF]ADD[/] [#96DCFE]file:c13b430c8699df107ffd9ea5230b92238bc037a8e1cbbe35d6ab664941d575da[/] [#96DCFE]/[/] 3 | [#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"bash"[/][#FAC81F]]][/] 4 | [#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 5 | apt-get update; [#FAC81F]\[/] 6 | apt-get install -y --no-install-recommends ca-certificates curl netbase wget ; [#FAC81F]\[/] 7 | rm -rf /var/lib/apt/lists/* 8 | [/][#C285BF]RUN[/] [#96DCFE]set -ex; [#FAC81F]\[/] 9 | if ! command -v gpg > /dev/null; then apt-get update; [#FAC81F]\[/] 10 | apt-get install -y --no-install-recommends gnupg dirmngr ; [#FAC81F]\[/] 11 | rm -rf /var/lib/apt/lists/*; [#FAC81F]\[/] 12 | fi 13 | [/][#C285BF]RUN[/] [#96DCFE]apt-get update && apt-get install -y --no-install-recommends git mercurial openssh-client subversion procps [#FAC81F]\[/] 14 | && rm -rf /var/lib/apt/lists/* 15 | [/][#C285BF]RUN[/] [#96DCFE]set -ex; [#FAC81F]\[/] 16 | apt-get update; [#FAC81F]\[/] 17 | apt-get install -y --no-install-recommends autoconf automake bzip2 dpkg-dev file g++ gcc imagemagick libbz2-dev libc6-dev libcurl4-openssl-dev libdb-dev libevent-dev libffi-dev libgdbm-dev libglib2.0-dev libgmp-dev libjpeg-dev libkrb5-dev liblzma-dev libmagickcore-dev libmagickwand-dev libmaxminddb-dev libncurses5-dev libncursesw5-dev libpng-dev libpq-dev libreadline-dev libsqlite3-dev libssl-dev libtool libwebp-dev libxml2-dev libxslt-dev libyaml-dev make patch unzip xz-utils zlib1g-dev $( if apt-cache show 'default-libmysqlclient-dev' 2>/dev/null | grep -q '^Version:'; then echo 'default-libmysqlclient-dev'; [#FAC81F]\[/] 18 | else echo 'libmysqlclient-dev'; [#FAC81F]\[/] 19 | fi ) ; [#FAC81F]\[/] 20 | rm -rf /var/lib/apt/lists/* 21 | [/][#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin[/] 22 | [#C285BF]ENV[/] [green]LANG[/][#FAC81F]=[/][#96DCFE]C.UTF-8[/] 23 | [#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 24 | apt-get update; [#FAC81F]\[/] 25 | apt-get install -y --no-install-recommends libbluetooth-dev tk-dev uuid-dev ; [#FAC81F]\[/] 26 | rm -rf /var/lib/apt/lists/* 27 | [/][#C285BF]ENV[/] [green]GPG_KEY[/][#FAC81F]=[/][#96DCFE]A035C8C19219BA821ECEA86B64E628F8D684696D[/] 28 | [#C285BF]ENV[/] [green]PYTHON_VERSION[/][#FAC81F]=[/][#96DCFE]3.11.1[/] 29 | [#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 30 | wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[[a-z]]*}/Python-$PYTHON_VERSION.tar.xz"; [#FAC81F]\[/] 31 | wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[[a-z]]*}/Python-$PYTHON_VERSION.tar.xz.asc"; [#FAC81F]\[/] 32 | GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; [#FAC81F]\[/] 33 | gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$GPG_KEY"; [#FAC81F]\[/] 34 | gpg --batch --verify python.tar.xz.asc python.tar.xz; [#FAC81F]\[/] 35 | command -v gpgconf > /dev/null && gpgconf --kill all || :; [#FAC81F]\[/] 36 | rm -rf "$GNUPGHOME" python.tar.xz.asc; [#FAC81F]\[/] 37 | mkdir -p /usr/src/python; [#FAC81F]\[/] 38 | tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; [#FAC81F]\[/] 39 | rm python.tar.xz; [#FAC81F]\[/] 40 | cd /usr/src/python; [#FAC81F]\[/] 41 | gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; [#FAC81F]\[/] 42 | ./configure --build="$gnuArch" --enable-loadable-sqlite-extensions --enable-optimizations --enable-option-checking=fatal --enable-shared --with-lto --with-system-expat --without-ensurepip ; [#FAC81F]\[/] 43 | nproc="$(nproc)"; [#FAC81F]\[/] 44 | make -j "$nproc" ; [#FAC81F]\[/] 45 | make install; [#FAC81F]\[/] 46 | bin="$(readlink -ve /usr/local/bin/python3)"; [#FAC81F]\[/] 47 | dir="$(dirname "$bin")"; [#FAC81F]\[/] 48 | mkdir -p "/usr/share/gdb/auto-load/$dir"; [#FAC81F]\[/] 49 | cp -vL Tools/gdb/libpython.py "/usr/share/gdb/auto-load/$bin-gdb.py"; [#FAC81F]\[/] 50 | cd /; [#FAC81F]\[/] 51 | rm -rf /usr/src/python; [#FAC81F]\[/] 52 | find /usr/local -depth \( \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) -o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \) \) \) -exec rm -rf '{}' + ; [#FAC81F]\[/] 53 | ldconfig; [#FAC81F]\[/] 54 | python3 --version 55 | [/][#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 56 | for src in idle3 pydoc3 python3 python3-config; do dst="$(echo "$src" | tr -d 3)"; [#FAC81F]\[/] 57 | [[ -s "/usr/local/bin/$src" ]]; [#FAC81F]\[/] 58 | [[ ! -e "/usr/local/bin/$dst" ]]; [#FAC81F]\[/] 59 | ln -svT "$src" "/usr/local/bin/$dst"; [#FAC81F]\[/] 60 | done 61 | [/][#C285BF]ENV[/] [green]PYTHON_PIP_VERSION[/][#FAC81F]=[/][#96DCFE]22.3.1[/] 62 | [#C285BF]ENV[/] [green]PYTHON_SETUPTOOLS_VERSION[/][#FAC81F]=[/][#96DCFE]65.5.1[/] 63 | [#C285BF]ENV[/] [green]PYTHON_GET_PIP_URL[/][#FAC81F]=[/][#96DCFE]https://github.com/pypa/get-pip/raw/66030fa03382b4914d4c4d0896961a0bdeeeb274/public/get-pip.py[/] 64 | [#C285BF]ENV[/] [green]PYTHON_GET_PIP_SHA256[/][#FAC81F]=[/][#96DCFE]1e501cf004eac1b7eb1f97266d28f995ae835d30250bec7f8850562703067dc6[/] 65 | [#C285BF]RUN[/] [#96DCFE]set -eux; [#FAC81F]\[/] 66 | wget -O get-pip.py "$PYTHON_GET_PIP_URL"; [#FAC81F]\[/] 67 | echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c -; [#FAC81F]\[/] 68 | export PYTHONDONTWRITEBYTECODE=1; [#FAC81F]\[/] 69 | python get-pip.py --disable-pip-version-check --no-cache-dir --no-compile "pip==$PYTHON_PIP_VERSION" "setuptools==$PYTHON_SETUPTOOLS_VERSION" ; [#FAC81F]\[/] 70 | rm -f get-pip.py; [#FAC81F]\[/] 71 | pip --version 72 | [/][#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"python3"[/][#FAC81F]]][/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/python/expected-output-no-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]scratch[/] 2 | [#C285BF]ADD[/] [#96DCFE]file:c13b430c8699df107ffd9ea5230b92238bc037a8e1cbbe35d6ab664941d575da[/] [#96DCFE]/[/] 3 | [#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"bash"[/][#FAC81F]]][/] 4 | [#C285BF]RUN[/] [#96DCFE]set -eux; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl netbase wget ; rm -rf /var/lib/apt/lists/* 5 | [/][#C285BF]RUN[/] [#96DCFE]set -ex; if ! command -v gpg > /dev/null; then apt-get update; apt-get install -y --no-install-recommends gnupg dirmngr ; rm -rf /var/lib/apt/lists/*; fi 6 | [/][#C285BF]RUN[/] [#96DCFE]apt-get update && apt-get install -y --no-install-recommends git mercurial openssh-client subversion procps && rm -rf /var/lib/apt/lists/* 7 | [/][#C285BF]RUN[/] [#96DCFE]set -ex; apt-get update; apt-get install -y --no-install-recommends autoconf automake bzip2 dpkg-dev file g++ gcc imagemagick libbz2-dev libc6-dev libcurl4-openssl-dev libdb-dev libevent-dev libffi-dev libgdbm-dev libglib2.0-dev libgmp-dev libjpeg-dev libkrb5-dev liblzma-dev libmagickcore-dev libmagickwand-dev libmaxminddb-dev libncurses5-dev libncursesw5-dev libpng-dev libpq-dev libreadline-dev libsqlite3-dev libssl-dev libtool libwebp-dev libxml2-dev libxslt-dev libyaml-dev make patch unzip xz-utils zlib1g-dev $( if apt-cache show 'default-libmysqlclient-dev' 2>/dev/null | grep -q '^Version:'; then echo 'default-libmysqlclient-dev'; else echo 'libmysqlclient-dev'; fi ) ; rm -rf /var/lib/apt/lists/* 8 | [/][#C285BF]ENV[/] [green]PATH[/][#FAC81F]=[/][#96DCFE]/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin[/] 9 | [#C285BF]ENV[/] [green]LANG[/][#FAC81F]=[/][#96DCFE]C.UTF-8[/] 10 | [#C285BF]RUN[/] [#96DCFE]set -eux; apt-get update; apt-get install -y --no-install-recommends libbluetooth-dev tk-dev uuid-dev ; rm -rf /var/lib/apt/lists/* 11 | [/][#C285BF]ENV[/] [green]GPG_KEY[/][#FAC81F]=[/][#96DCFE]A035C8C19219BA821ECEA86B64E628F8D684696D[/] 12 | [#C285BF]ENV[/] [green]PYTHON_VERSION[/][#FAC81F]=[/][#96DCFE]3.11.1[/] 13 | [#C285BF]RUN[/] [#96DCFE]set -eux; wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[[a-z]]*}/Python-$PYTHON_VERSION.tar.xz"; wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[[a-z]]*}/Python-$PYTHON_VERSION.tar.xz.asc"; GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$GPG_KEY"; gpg --batch --verify python.tar.xz.asc python.tar.xz; command -v gpgconf > /dev/null && gpgconf --kill all || :; rm -rf "$GNUPGHOME" python.tar.xz.asc; mkdir -p /usr/src/python; tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; rm python.tar.xz; cd /usr/src/python; gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; ./configure --build="$gnuArch" --enable-loadable-sqlite-extensions --enable-optimizations --enable-option-checking=fatal --enable-shared --with-lto --with-system-expat --without-ensurepip ; nproc="$(nproc)"; make -j "$nproc" ; make install; bin="$(readlink -ve /usr/local/bin/python3)"; dir="$(dirname "$bin")"; mkdir -p "/usr/share/gdb/auto-load/$dir"; cp -vL Tools/gdb/libpython.py "/usr/share/gdb/auto-load/$bin-gdb.py"; cd /; rm -rf /usr/src/python; find /usr/local -depth \( \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) -o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \) \) \) -exec rm -rf '{}' + ; ldconfig; python3 --version 14 | [/][#C285BF]RUN[/] [#96DCFE]set -eux; for src in idle3 pydoc3 python3 python3-config; do dst="$(echo "$src" | tr -d 3)"; [[ -s "/usr/local/bin/$src" ]]; [[ ! -e "/usr/local/bin/$dst" ]]; ln -svT "$src" "/usr/local/bin/$dst"; done 15 | [/][#C285BF]ENV[/] [green]PYTHON_PIP_VERSION[/][#FAC81F]=[/][#96DCFE]22.3.1[/] 16 | [#C285BF]ENV[/] [green]PYTHON_SETUPTOOLS_VERSION[/][#FAC81F]=[/][#96DCFE]65.5.1[/] 17 | [#C285BF]ENV[/] [green]PYTHON_GET_PIP_URL[/][#FAC81F]=[/][#96DCFE]https://github.com/pypa/get-pip/raw/66030fa03382b4914d4c4d0896961a0bdeeeb274/public/get-pip.py[/] 18 | [#C285BF]ENV[/] [green]PYTHON_GET_PIP_SHA256[/][#FAC81F]=[/][#96DCFE]1e501cf004eac1b7eb1f97266d28f995ae835d30250bec7f8850562703067dc6[/] 19 | [#C285BF]RUN[/] [#96DCFE]set -eux; wget -O get-pip.py "$PYTHON_GET_PIP_URL"; echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c -; export PYTHONDONTWRITEBYTECODE=1; python get-pip.py --disable-pip-version-check --no-cache-dir --no-compile "pip==$PYTHON_PIP_VERSION" "setuptools==$PYTHON_SETUPTOOLS_VERSION" ; rm -f get-pip.py; pip --version 20 | [/][#C285BF]CMD[/] [#FAC81F][[[/][#CA9178]"python3"[/][#FAC81F]]][/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/python/image.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "library/python@sha256:779bcaad95871999fb6734a3923ff6a09cf6459231913d706b302eef5b6e383e", 3 | "os": "linux", 4 | "history": [ 5 | { 6 | "created": "2022-12-21T01:20:21.922936512Z", 7 | "created_by": "/bin/sh -c #(nop) ADD file:c13b430c8699df107ffd9ea5230b92238bc037a8e1cbbe35d6ab664941d575da in / " 8 | }, 9 | { 10 | "created": "2022-12-21T01:20:22.590344295Z", 11 | "created_by": "/bin/sh -c #(nop) CMD [\"bash\"]", 12 | "empty_layer": true 13 | }, 14 | { 15 | "created": "2022-12-21T11:13:51.512187477Z", 16 | "created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\tcurl \t\tnetbase \t\twget \t; \trm -rf /var/lib/apt/lists/*" 17 | }, 18 | { 19 | "created": "2022-12-21T11:13:58.806979115Z", 20 | "created_by": "/bin/sh -c set -ex; \tif ! command -v gpg > /dev/null; then \t\tapt-get update; \t\tapt-get install -y --no-install-recommends \t\t\tgnupg \t\t\tdirmngr \t\t; \t\trm -rf /var/lib/apt/lists/*; \tfi" 21 | }, 22 | { 23 | "created": "2022-12-21T11:14:17.676013622Z", 24 | "created_by": "/bin/sh -c apt-get update && apt-get install -y --no-install-recommends \t\tgit \t\tmercurial \t\topenssh-client \t\tsubversion \t\t\t\tprocps \t&& rm -rf /var/lib/apt/lists/*" 25 | }, 26 | { 27 | "created": "2022-12-21T11:15:31.49682344Z", 28 | "created_by": "/bin/sh -c set -ex; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tautoconf \t\tautomake \t\tbzip2 \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\timagemagick \t\tlibbz2-dev \t\tlibc6-dev \t\tlibcurl4-openssl-dev \t\tlibdb-dev \t\tlibevent-dev \t\tlibffi-dev \t\tlibgdbm-dev \t\tlibglib2.0-dev \t\tlibgmp-dev \t\tlibjpeg-dev \t\tlibkrb5-dev \t\tliblzma-dev \t\tlibmagickcore-dev \t\tlibmagickwand-dev \t\tlibmaxminddb-dev \t\tlibncurses5-dev \t\tlibncursesw5-dev \t\tlibpng-dev \t\tlibpq-dev \t\tlibreadline-dev \t\tlibsqlite3-dev \t\tlibssl-dev \t\tlibtool \t\tlibwebp-dev \t\tlibxml2-dev \t\tlibxslt-dev \t\tlibyaml-dev \t\tmake \t\tpatch \t\tunzip \t\txz-utils \t\tzlib1g-dev \t\t\t\t$( \t\t\tif apt-cache show 'default-libmysqlclient-dev' 2>/dev/null | grep -q '^Version:'; then \t\t\t\techo 'default-libmysqlclient-dev'; \t\t\telse \t\t\t\techo 'libmysqlclient-dev'; \t\t\tfi \t\t) \t; \trm -rf /var/lib/apt/lists/*" 29 | }, 30 | { 31 | "created": "2022-12-21T17:54:25.078912736Z", 32 | "created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 33 | "empty_layer": true 34 | }, 35 | { 36 | "created": "2022-12-21T17:54:25.176780719Z", 37 | "created_by": "/bin/sh -c #(nop) ENV LANG=C.UTF-8", 38 | "empty_layer": true 39 | }, 40 | { 41 | "created": "2022-12-21T17:54:30.87196833Z", 42 | "created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tlibbluetooth-dev \t\ttk-dev \t\tuuid-dev \t; \trm -rf /var/lib/apt/lists/*" 43 | }, 44 | { 45 | "created": "2022-12-21T18:22:49.128929869Z", 46 | "created_by": "/bin/sh -c #(nop) ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D", 47 | "empty_layer": true 48 | }, 49 | { 50 | "created": "2022-12-21T18:22:49.227106757Z", 51 | "created_by": "/bin/sh -c #(nop) ENV PYTHON_VERSION=3.11.1", 52 | "empty_layer": true 53 | }, 54 | { 55 | "created": "2022-12-21T18:36:43.481204294Z", 56 | "created_by": "/bin/sh -c set -eux; \t\twget -O python.tar.xz \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz\"; \twget -O python.tar.xz.asc \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc\"; \tGNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys \"$GPG_KEY\"; \tgpg --batch --verify python.tar.xz.asc python.tar.xz; \tcommand -v gpgconf > /dev/null && gpgconf --kill all || :; \trm -rf \"$GNUPGHOME\" python.tar.xz.asc; \tmkdir -p /usr/src/python; \ttar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \trm python.tar.xz; \t\tcd /usr/src/python; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \t./configure \t\t--build=\"$gnuArch\" \t\t--enable-loadable-sqlite-extensions \t\t--enable-optimizations \t\t--enable-option-checking=fatal \t\t--enable-shared \t\t--with-lto \t\t--with-system-expat \t\t--without-ensurepip \t; \tnproc=\"$(nproc)\"; \tmake -j \"$nproc\" \t; \tmake install; \t\tbin=\"$(readlink -ve /usr/local/bin/python3)\"; \tdir=\"$(dirname \"$bin\")\"; \tmkdir -p \"/usr/share/gdb/auto-load/$dir\"; \tcp -vL Tools/gdb/libpython.py \"/usr/share/gdb/auto-load/$bin-gdb.py\"; \t\tcd /; \trm -rf /usr/src/python; \t\tfind /usr/local -depth \t\t\\( \t\t\t\\( -type d -a \\( -name test -o -name tests -o -name idle_test \\) \\) \t\t\t-o \\( -type f -a \\( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \\) \\) \t\t\\) -exec rm -rf '{}' + \t; \t\tldconfig; \t\tpython3 --version" 57 | }, 58 | { 59 | "created": "2022-12-21T18:36:44.251539564Z", 60 | "created_by": "/bin/sh -c set -eux; \tfor src in idle3 pydoc3 python3 python3-config; do \t\tdst=\"$(echo \"$src\" | tr -d 3)\"; \t\t[ -s \"/usr/local/bin/$src\" ]; \t\t[ ! -e \"/usr/local/bin/$dst\" ]; \t\tln -svT \"$src\" \"/usr/local/bin/$dst\"; \tdone" 61 | }, 62 | { 63 | "created": "2022-12-21T18:36:44.351889165Z", 64 | "created_by": "/bin/sh -c #(nop) ENV PYTHON_PIP_VERSION=22.3.1", 65 | "empty_layer": true 66 | }, 67 | { 68 | "created": "2023-01-06T18:41:34.9197611Z", 69 | "created_by": "/bin/sh -c #(nop) ENV PYTHON_SETUPTOOLS_VERSION=65.5.1", 70 | "empty_layer": true 71 | }, 72 | { 73 | "created": "2023-01-06T18:41:35.026340408Z", 74 | "created_by": "/bin/sh -c #(nop) ENV PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/66030fa03382b4914d4c4d0896961a0bdeeeb274/public/get-pip.py", 75 | "empty_layer": true 76 | }, 77 | { 78 | "created": "2023-01-06T18:41:35.138016472Z", 79 | "created_by": "/bin/sh -c #(nop) ENV PYTHON_GET_PIP_SHA256=1e501cf004eac1b7eb1f97266d28f995ae835d30250bec7f8850562703067dc6", 80 | "empty_layer": true 81 | }, 82 | { 83 | "created": "2023-01-06T18:41:40.450192701Z", 84 | "created_by": "/bin/sh -c set -eux; \t\twget -O get-pip.py \"$PYTHON_GET_PIP_URL\"; \techo \"$PYTHON_GET_PIP_SHA256 *get-pip.py\" | sha256sum -c -; \t\texport PYTHONDONTWRITEBYTECODE=1; \t\tpython get-pip.py \t\t--disable-pip-version-check \t\t--no-cache-dir \t\t--no-compile \t\t\"pip==$PYTHON_PIP_VERSION\" \t\t\"setuptools==$PYTHON_SETUPTOOLS_VERSION\" \t; \trm -f get-pip.py; \t\tpip --version" 85 | }, 86 | { 87 | "created": "2023-01-06T18:41:40.59753588Z", 88 | "created_by": "/bin/sh -c #(nop) CMD [\"python3\"]", 89 | "empty_layer": true 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/windows/expected-output-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]mcr.microsoft.com/windows/servercore:10.0.20348.1366-amd64[/] 2 | [#C285BF]ENV[/] [green]ASPNETCORE_URLS[/][#FAC81F]=[/][#96DCFE]http://+:80[/] [#FAC81F]\[/] 3 | [green]DOTNET_RUNNING_IN_CONTAINER[/][#FAC81F]=[/][#96DCFE]true[/] [#FAC81F]\[/] 4 | [green]DOTNET_VERSION[/][#FAC81F]=[/][#96DCFE]7.0.1[/] 5 | [#C285BF]RUN[/] [#96DCFE]powershell -Command $ErrorActionPreference = 'Stop'; [#FAC81F]\[/] 6 | $ProgressPreference = 'SilentlyContinue'; [#FAC81F]\[/] 7 | Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Runtime/$Env:DOTNET_VERSION/dotnet-runtime-$Env:DOTNET_VERSION-win-x64.zip; [#FAC81F]\[/] 8 | $dotnet_sha512 = '36a2245abc70c794282a7e6f270585baccd074875faff09b6eccff1c7ac8fada782951bc0f34bdc4bb33794508660346daa39c03aa0a45313e109c05eb98bd13'; [#FAC81F]\[/] 9 | if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512){ [#FAC81F]\[/] 10 | Write-Host 'CHECKSUM VERIFICATION FAILED!'; [#FAC81F]\[/] 11 | exit 1; [#FAC81F]\[/] 12 | }; [#FAC81F]\[/] 13 | mkdir $Env:ProgramFiles\dotnet; [#FAC81F]\[/] 14 | tar -oxzf dotnet.zip -C $Env:ProgramFiles\dotnet; [#FAC81F]\[/] 15 | Remove-Item -Force dotnet.zip 16 | [/][#C285BF]RUN[/] [#96DCFE]setx /M PATH "%PATH%;C:\Program Files\dotnet" 17 | [/][#C285BF]ENV[/] [green]ASPNET_VERSION[/][#FAC81F]=[/][#96DCFE]7.0.1[/] 18 | [#C285BF]RUN[/] [#96DCFE]powershell -Command $ErrorActionPreference = 'Stop'; [#FAC81F]\[/] 19 | $ProgressPreference = 'SilentlyContinue'; [#FAC81F]\[/] 20 | Invoke-WebRequest -OutFile aspnetcore.zip https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$Env:ASPNET_VERSION/aspnetcore-runtime-$Env:ASPNET_VERSION-win-x64.zip; [#FAC81F]\[/] 21 | $aspnetcore_sha512 = '7afa7bb9febabe32b9a38ac9a6376e342cb08c6d1f967647575b49995dd74feafdd6e6723688e1378a2edbd7e893ce15a75c74bec534303f0e60356ccf29d330'; [#FAC81F]\[/] 22 | if ((Get-FileHash aspnetcore.zip -Algorithm sha512).Hash -ne $aspnetcore_sha512){ [#FAC81F]\[/] 23 | Write-Host 'CHECKSUM VERIFICATION FAILED!'; [#FAC81F]\[/] 24 | exit 1; [#FAC81F]\[/] 25 | }; [#FAC81F]\[/] 26 | tar -oxzf aspnetcore.zip -C $Env:ProgramFiles\dotnet ./shared/Microsoft.AspNetCore.App; [#FAC81F]\[/] 27 | Remove-Item -Force aspnetcore.zip 28 | [/][#C285BF]ENV[/] [green]ASPNETCORE_URLS[/][#FAC81F]=[/][#96DCFE][/] [#FAC81F]\[/] 29 | [green]DOTNET_GENERATE_ASPNET_CERTIFICATE[/][#FAC81F]=[/][#96DCFE]false[/] [#FAC81F]\[/] 30 | [green]DOTNET_NOLOGO[/][#FAC81F]=[/][#96DCFE]true[/] [#FAC81F]\[/] 31 | [green]DOTNET_SDK_VERSION[/][#FAC81F]=[/][#96DCFE]7.0.101[/] [#FAC81F]\[/] 32 | [green]DOTNET_USE_POLLING_FILE_WATCHER[/][#FAC81F]=[/][#96DCFE]true[/] [#FAC81F]\[/] 33 | [green]NUGET_XMLDOC_MODE[/][#FAC81F]=[/][#96DCFE]skip[/] [#FAC81F]\[/] 34 | [green]POWERSHELL_DISTRIBUTION_CHANNEL[/][#FAC81F]=[/][#96DCFE]PSDocker-DotnetSDK-WindowsServerCore-ltsc2022[/] 35 | [#C285BF]RUN[/] [#96DCFE]powershell -Command " $ErrorActionPreference = 'Stop'; [#FAC81F]\[/] 36 | $ProgressPreference = 'SilentlyContinue'; [#FAC81F]\[/] 37 | Invoke-WebRequest -OutFile mingit.zip https://github.com/git-for-windows/git/releases/download/v2.37.3.windows.1/MinGit-2.37.3-64-bit.zip; [#FAC81F]\[/] 38 | $mingit_sha256 = 'cec8d038fadbdd82e269a5c458fd2a62711c1bb9a76c85f07c46de3bff6cdf32'; [#FAC81F]\[/] 39 | if ((Get-FileHash mingit.zip -Algorithm sha256).Hash -ne $mingit_sha256){ [#FAC81F]\[/] 40 | Write-Host 'CHECKSUM VERIFICATION FAILED!'; [#FAC81F]\[/] 41 | exit 1; [#FAC81F]\[/] 42 | }; [#FAC81F]\[/] 43 | mkdir $Env:ProgramFiles\MinGit; [#FAC81F]\[/] 44 | tar -oxzf mingit.zip -C $Env:ProgramFiles\MinGit; [#FAC81F]\[/] 45 | Remove-Item -Force mingit.zip" 46 | [/][#C285BF]RUN[/] [#96DCFE]powershell -Command " $ErrorActionPreference = 'Stop'; [#FAC81F]\[/] 47 | $ProgressPreference = 'SilentlyContinue'; [#FAC81F]\[/] 48 | Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Sdk/$Env:DOTNET_SDK_VERSION/dotnet-sdk-$Env:DOTNET_SDK_VERSION-win-x64.zip; [#FAC81F]\[/] 49 | $dotnet_sha512 = 'f7083e2fef2f5c93c7d899cdf047f5c88626603ad0fdddf1f176820b74a32e3fcfb2402aef49406765ac8f160b5b48a714f09db2cce0ed04575f71dc6a49eaed'; [#FAC81F]\[/] 50 | if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512){ [#FAC81F]\[/] 51 | Write-Host 'CHECKSUM VERIFICATION FAILED!'; [#FAC81F]\[/] 52 | exit 1; [#FAC81F]\[/] 53 | }; [#FAC81F]\[/] 54 | tar -oxzf dotnet.zip -C $Env:ProgramFiles\dotnet ./LICENSE.txt ./ThirdPartyNotices.txt ./packs ./sdk ./sdk-manifests ./templates ./shared/Microsoft.WindowsDesktop.App; [#FAC81F]\[/] 55 | Remove-Item -Force dotnet.zip; [#FAC81F]\[/] 56 | $powershell_version = '7.3.0'; [#FAC81F]\[/] 57 | Invoke-WebRequest -OutFile PowerShell.Windows.x64.$powershell_version.nupkg https://pwshtool.blob.core.windows.net/tool/$powershell_version/PowerShell.Windows.x64.$powershell_version.nupkg; [#FAC81F]\[/] 58 | $powershell_sha512 = '5c5459e739c9abb2eb72249158af9dd868823ed6200a33d07385f0b37c4405b490c0b40f4ababd850d72721f884b41b86b3c8d8039e5bf1efb3aa84c72162cdf'; [#FAC81F]\[/] 59 | if ((Get-FileHash PowerShell.Windows.x64.$powershell_version.nupkg -Algorithm sha512).Hash -ne $powershell_sha512){ [#FAC81F]\[/] 60 | Write-Host 'CHECKSUM VERIFICATION FAILED!'; [#FAC81F]\[/] 61 | exit 1; [#FAC81F]\[/] 62 | }; [#FAC81F]\[/] 63 | & $Env:ProgramFiles\dotnet\dotnet tool install --add-source . --tool-path $Env:ProgramFiles\powershell --version $powershell_version PowerShell.Windows.x64; [#FAC81F]\[/] 64 | & $Env:ProgramFiles\dotnet\dotnet nuget locals all --clear; [#FAC81F]\[/] 65 | Remove-Item -Force PowerShell.Windows.x64.$powershell_version.nupkg; [#FAC81F]\[/] 66 | Remove-Item -Path $Env:ProgramFiles\powershell\.store\powershell.windows.x64\$powershell_version\powershell.windows.x64\$powershell_version\powershell.windows.x64.$powershell_version.nupkg -Force;" 67 | [/][#C285BF]RUN[/] [#96DCFE]setx /M PATH "%PATH%;C:\Program Files\powershell;C:\Program Files\MinGit\cmd" 68 | [/][#C285BF]RUN[/] [#96DCFE]dotnet help 69 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/windows/expected-output-no-format.txt: -------------------------------------------------------------------------------- 1 | [#C285BF]FROM[/] [#96DCFE]mcr.microsoft.com/windows/servercore:10.0.20348.1366-amd64[/] 2 | [#C285BF]ENV[/] [green]ASPNETCORE_URLS[/][#FAC81F]=[/][#96DCFE]http://+:80[/] [green]DOTNET_RUNNING_IN_CONTAINER[/][#FAC81F]=[/][#96DCFE]true[/] [green]DOTNET_VERSION[/][#FAC81F]=[/][#96DCFE]7.0.1[/] 3 | [#C285BF]RUN[/] [#96DCFE]powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Runtime/$Env:DOTNET_VERSION/dotnet-runtime-$Env:DOTNET_VERSION-win-x64.zip; $dotnet_sha512 = '36a2245abc70c794282a7e6f270585baccd074875faff09b6eccff1c7ac8fada782951bc0f34bdc4bb33794508660346daa39c03aa0a45313e109c05eb98bd13'; if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; mkdir $Env:ProgramFiles\dotnet; tar -oxzf dotnet.zip -C $Env:ProgramFiles\dotnet; Remove-Item -Force dotnet.zip 4 | [/][#C285BF]RUN[/] [#96DCFE]setx /M PATH "%PATH%;C:\Program Files\dotnet" 5 | [/][#C285BF]ENV[/] [green]ASPNET_VERSION[/][#FAC81F]=[/][#96DCFE]7.0.1[/] 6 | [#C285BF]RUN[/] [#96DCFE]powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile aspnetcore.zip https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$Env:ASPNET_VERSION/aspnetcore-runtime-$Env:ASPNET_VERSION-win-x64.zip; $aspnetcore_sha512 = '7afa7bb9febabe32b9a38ac9a6376e342cb08c6d1f967647575b49995dd74feafdd6e6723688e1378a2edbd7e893ce15a75c74bec534303f0e60356ccf29d330'; if ((Get-FileHash aspnetcore.zip -Algorithm sha512).Hash -ne $aspnetcore_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; tar -oxzf aspnetcore.zip -C $Env:ProgramFiles\dotnet ./shared/Microsoft.AspNetCore.App; Remove-Item -Force aspnetcore.zip 7 | [/][#C285BF]ENV[/] [green]ASPNETCORE_URLS[/][#FAC81F]=[/][#96DCFE][/] [green]DOTNET_GENERATE_ASPNET_CERTIFICATE[/][#FAC81F]=[/][#96DCFE]false[/] [green]DOTNET_NOLOGO[/][#FAC81F]=[/][#96DCFE]true[/] [green]DOTNET_SDK_VERSION[/][#FAC81F]=[/][#96DCFE]7.0.101[/] [green]DOTNET_USE_POLLING_FILE_WATCHER[/][#FAC81F]=[/][#96DCFE]true[/] [green]NUGET_XMLDOC_MODE[/][#FAC81F]=[/][#96DCFE]skip[/] [green]POWERSHELL_DISTRIBUTION_CHANNEL[/][#FAC81F]=[/][#96DCFE]PSDocker-DotnetSDK-WindowsServerCore-ltsc2022[/] 8 | [#C285BF]RUN[/] [#96DCFE]powershell -Command " $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile mingit.zip https://github.com/git-for-windows/git/releases/download/v2.37.3.windows.1/MinGit-2.37.3-64-bit.zip; $mingit_sha256 = 'cec8d038fadbdd82e269a5c458fd2a62711c1bb9a76c85f07c46de3bff6cdf32'; if ((Get-FileHash mingit.zip -Algorithm sha256).Hash -ne $mingit_sha256) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; mkdir $Env:ProgramFiles\MinGit; tar -oxzf mingit.zip -C $Env:ProgramFiles\MinGit; Remove-Item -Force mingit.zip" 9 | [/][#C285BF]RUN[/] [#96DCFE]powershell -Command " $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Sdk/$Env:DOTNET_SDK_VERSION/dotnet-sdk-$Env:DOTNET_SDK_VERSION-win-x64.zip; $dotnet_sha512 = 'f7083e2fef2f5c93c7d899cdf047f5c88626603ad0fdddf1f176820b74a32e3fcfb2402aef49406765ac8f160b5b48a714f09db2cce0ed04575f71dc6a49eaed'; if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; tar -oxzf dotnet.zip -C $Env:ProgramFiles\dotnet ./LICENSE.txt ./ThirdPartyNotices.txt ./packs ./sdk ./sdk-manifests ./templates ./shared/Microsoft.WindowsDesktop.App; Remove-Item -Force dotnet.zip; $powershell_version = '7.3.0'; Invoke-WebRequest -OutFile PowerShell.Windows.x64.$powershell_version.nupkg https://pwshtool.blob.core.windows.net/tool/$powershell_version/PowerShell.Windows.x64.$powershell_version.nupkg; $powershell_sha512 = '5c5459e739c9abb2eb72249158af9dd868823ed6200a33d07385f0b37c4405b490c0b40f4ababd850d72721f884b41b86b3c8d8039e5bf1efb3aa84c72162cdf'; if ((Get-FileHash PowerShell.Windows.x64.$powershell_version.nupkg -Algorithm sha512).Hash -ne $powershell_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; & $Env:ProgramFiles\dotnet\dotnet tool install --add-source . --tool-path $Env:ProgramFiles\powershell --version $powershell_version PowerShell.Windows.x64; & $Env:ProgramFiles\dotnet\dotnet nuget locals all --clear; Remove-Item -Force PowerShell.Windows.x64.$powershell_version.nupkg; Remove-Item -Path $Env:ProgramFiles\powershell\.store\powershell.windows.x64\$powershell_version\powershell.windows.x64\$powershell_version\powershell.windows.x64.$powershell_version.nupkg -Force;" 10 | [/][#C285BF]RUN[/] [#96DCFE]setx /M PATH "%PATH%;C:\Program Files\powershell;C:\Program Files\MinGit\cmd" 11 | [/][#C285BF]RUN[/] [#96DCFE]dotnet help 12 | [/] -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestData/DockerfileCommand/windows/image.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "mcr.microsoft.com/dotnet/sdk@sha256:321018d653470f9d9b4da5d2aea9915916752ce6a4ef9806bfb68046aed62053", 3 | "os": "windows", 4 | "os.version": "10.0.20348.1366", 5 | "architecture": "amd64", 6 | "history": [ 7 | { 8 | "created": "2022-04-22T01:12:09.4542389Z", 9 | "created_by": "Apply image 10.0.20348.643" 10 | }, 11 | { 12 | "created": "2022-12-09T09:36:47.5016031Z", 13 | "created_by": "Install update 10.0.20348.1366" 14 | }, 15 | { 16 | "created": "2022-12-13T18:38:52.9281645Z", 17 | "created_by": "cmd /S /C #(nop) ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true DOTNET_VERSION=7.0.1" 18 | }, 19 | { 20 | "created": "2022-12-13T18:39:21.1523699Z", 21 | "created_by": "cmd /S /C powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Runtime/$Env:DOTNET_VERSION/dotnet-runtime-$Env:DOTNET_VERSION-win-x64.zip; $dotnet_sha512 = '36a2245abc70c794282a7e6f270585baccd074875faff09b6eccff1c7ac8fada782951bc0f34bdc4bb33794508660346daa39c03aa0a45313e109c05eb98bd13'; if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; mkdir $Env:ProgramFiles\\dotnet; tar -oxzf dotnet.zip -C $Env:ProgramFiles\\dotnet; Remove-Item -Force dotnet.zip" 22 | }, 23 | { 24 | "created": "2022-12-13T18:39:35.052291Z", 25 | "created_by": "cmd /S /C setx /M PATH \"%PATH%;C:\\Program Files\\dotnet\"" 26 | }, 27 | { 28 | "created": "2022-12-13T18:39:38.3216918Z", 29 | "created_by": "cmd /S /C #(nop) ENV ASPNET_VERSION=7.0.1" 30 | }, 31 | { 32 | "created": "2022-12-13T18:40:02.618451Z", 33 | "created_by": "cmd /S /C powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile aspnetcore.zip https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$Env:ASPNET_VERSION/aspnetcore-runtime-$Env:ASPNET_VERSION-win-x64.zip; $aspnetcore_sha512 = '7afa7bb9febabe32b9a38ac9a6376e342cb08c6d1f967647575b49995dd74feafdd6e6723688e1378a2edbd7e893ce15a75c74bec534303f0e60356ccf29d330'; if ((Get-FileHash aspnetcore.zip -Algorithm sha512).Hash -ne $aspnetcore_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; tar -oxzf aspnetcore.zip -C $Env:ProgramFiles\\dotnet ./shared/Microsoft.AspNetCore.App; Remove-Item -Force aspnetcore.zip" 34 | }, 35 | { 36 | "created": "2022-12-13T18:40:05.0965059Z", 37 | "created_by": "cmd /S /C #(nop) ENV ASPNETCORE_URLS= DOTNET_GENERATE_ASPNET_CERTIFICATE=false DOTNET_NOLOGO=true DOTNET_SDK_VERSION=7.0.101 DOTNET_USE_POLLING_FILE_WATCHER=true NUGET_XMLDOC_MODE=skip POWERSHELL_DISTRIBUTION_CHANNEL=PSDocker-DotnetSDK-WindowsServerCore-ltsc2022" 38 | }, 39 | { 40 | "created": "2022-12-13T18:40:37.2184725Z", 41 | "created_by": "cmd /S /C powershell -Command \" $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile mingit.zip https://github.com/git-for-windows/git/releases/download/v2.37.3.windows.1/MinGit-2.37.3-64-bit.zip; $mingit_sha256 = 'cec8d038fadbdd82e269a5c458fd2a62711c1bb9a76c85f07c46de3bff6cdf32'; if ((Get-FileHash mingit.zip -Algorithm sha256).Hash -ne $mingit_sha256) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; mkdir $Env:ProgramFiles\\MinGit; tar -oxzf mingit.zip -C $Env:ProgramFiles\\MinGit; Remove-Item -Force mingit.zip\"" 42 | }, 43 | { 44 | "created": "2022-12-13T18:41:59.2475589Z", 45 | "created_by": "cmd /S /C powershell -Command \" $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Sdk/$Env:DOTNET_SDK_VERSION/dotnet-sdk-$Env:DOTNET_SDK_VERSION-win-x64.zip; $dotnet_sha512 = 'f7083e2fef2f5c93c7d899cdf047f5c88626603ad0fdddf1f176820b74a32e3fcfb2402aef49406765ac8f160b5b48a714f09db2cce0ed04575f71dc6a49eaed'; if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; tar -oxzf dotnet.zip -C $Env:ProgramFiles\\dotnet ./LICENSE.txt ./ThirdPartyNotices.txt ./packs ./sdk ./sdk-manifests ./templates ./shared/Microsoft.WindowsDesktop.App; Remove-Item -Force dotnet.zip; $powershell_version = '7.3.0'; Invoke-WebRequest -OutFile PowerShell.Windows.x64.$powershell_version.nupkg https://pwshtool.blob.core.windows.net/tool/$powershell_version/PowerShell.Windows.x64.$powershell_version.nupkg; $powershell_sha512 = '5c5459e739c9abb2eb72249158af9dd868823ed6200a33d07385f0b37c4405b490c0b40f4ababd850d72721f884b41b86b3c8d8039e5bf1efb3aa84c72162cdf'; if ((Get-FileHash PowerShell.Windows.x64.$powershell_version.nupkg -Algorithm sha512).Hash -ne $powershell_sha512) { Write-Host 'CHECKSUM VERIFICATION FAILED!'; exit 1; }; & $Env:ProgramFiles\\dotnet\\dotnet tool install --add-source . --tool-path $Env:ProgramFiles\\powershell --version $powershell_version PowerShell.Windows.x64; & $Env:ProgramFiles\\dotnet\\dotnet nuget locals all --clear; Remove-Item -Force PowerShell.Windows.x64.$powershell_version.nupkg; Remove-Item -Path $Env:ProgramFiles\\powershell\\.store\\powershell.windows.x64\\$powershell_version\\powershell.windows.x64\\$powershell_version\\powershell.windows.x64.$powershell_version.nupkg -Force;\"" 46 | }, 47 | { 48 | "created": "2022-12-13T18:42:11.406177Z", 49 | "created_by": "cmd /S /C setx /M PATH \"%PATH%;C:\\Program Files\\powershell;C:\\Program Files\\MinGit\\cmd\"" 50 | }, 51 | { 52 | "created": "2022-12-13T18:42:24.3686611Z", 53 | "created_by": "cmd /S /C dotnet help" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using Spectre.Console.Rendering; 2 | using System.Text; 3 | 4 | namespace Valleysoft.Dredge.Tests; 5 | 6 | public static class TestHelper 7 | { 8 | public static string GetString(IEnumerable segments) 9 | { 10 | StringBuilder builder = new(); 11 | foreach (Segment segment in segments) 12 | { 13 | builder.Append(segment.Text); 14 | } 15 | return builder.ToString(); 16 | } 17 | 18 | public static string Normalize(string val) => 19 | val.Replace("\r", string.Empty).TrimEnd(); 20 | } 21 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Moq; 2 | global using Xunit; -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.Tests/Valleysoft.Dredge.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | false 9 | IDE0290;xUnit1047 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | PreserveNewest 39 | 40 | 41 | PreserveNewest 42 | 43 | 44 | PreserveNewest 45 | 46 | 47 | PreserveNewest 48 | 49 | 50 | PreserveNewest 51 | 52 | 53 | PreserveNewest 54 | 55 | 56 | PreserveNewest 57 | 58 | 59 | PreserveNewest 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | PreserveNewest 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | PreserveNewest 75 | 76 | 77 | PreserveNewest 78 | 79 | 80 | PreserveNewest 81 | 82 | 83 | PreserveNewest 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33109.374 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Valleysoft.Dredge", "Valleysoft.Dredge\Valleysoft.Dredge.csproj", "{781B651C-A6F3-445B-AD9D-ABB5E5632445}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Valleysoft.Dredge.Tests", "Valleysoft.Dredge.Tests\Valleysoft.Dredge.Tests.csproj", "{39FD6D54-14E0-4894-95AC-951E3209A59F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valleysoft.Dredge.Analyzers", "Valleysoft.Dredge.Analyzers\Valleysoft.Dredge.Analyzers.csproj", "{23CD573A-1001-4856-A9E5-188043E314CE}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {781B651C-A6F3-445B-AD9D-ABB5E5632445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {781B651C-A6F3-445B-AD9D-ABB5E5632445}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {781B651C-A6F3-445B-AD9D-ABB5E5632445}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {781B651C-A6F3-445B-AD9D-ABB5E5632445}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {39FD6D54-14E0-4894-95AC-951E3209A59F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {39FD6D54-14E0-4894-95AC-951E3209A59F}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {39FD6D54-14E0-4894-95AC-951E3209A59F}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {39FD6D54-14E0-4894-95AC-951E3209A59F}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {23CD573A-1001-4856-A9E5-188043E314CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {23CD573A-1001-4856-A9E5-188043E314CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {23CD573A-1001-4856-A9E5-188043E314CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {23CD573A-1001-4856-A9E5-188043E314CE}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {5097BADF-0AD9-4D7F-BFB7-2991DAEC0C5F} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Valleysoft.Dredge; 4 | 5 | internal partial class AppSettings 6 | { 7 | public static readonly string SettingsPath = 8 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Valleysoft.Dredge", "settings.json"); 9 | 10 | public const string FileCompareToolName = "fileCompareTool"; 11 | 12 | [JsonProperty(FileCompareToolName)] 13 | public FileCompareToolSettings FileCompareTool { get; set; } = new(); 14 | 15 | [JsonProperty("platform")] 16 | public PlatformSettings Platform { get; set; } = new(); 17 | 18 | private AppSettings() {} 19 | 20 | public static AppSettings Load() 21 | { 22 | if (!File.Exists(SettingsPath)) 23 | { 24 | AppSettings settings = new(); 25 | string settingsStr = JsonConvert.SerializeObject(settings, JsonHelper.Settings); 26 | 27 | string dirName = Path.GetDirectoryName(SettingsPath)!; 28 | if (!Directory.Exists(dirName)) 29 | { 30 | Directory.CreateDirectory(dirName); 31 | } 32 | 33 | File.WriteAllText(SettingsPath, settingsStr); 34 | return settings; 35 | } 36 | else 37 | { 38 | string settings = File.ReadAllText(SettingsPath); 39 | return JsonConvert.DeserializeObject(settings)!; 40 | } 41 | } 42 | 43 | public void Save() 44 | { 45 | string settingsStr = JsonConvert.SerializeObject(this, JsonHelper.Settings); 46 | File.WriteAllText(SettingsPath, settingsStr); 47 | } 48 | } 49 | 50 | internal partial class FileCompareToolSettings 51 | { 52 | [JsonProperty("exePath")] 53 | public string ExePath { get; set; } = string.Empty; 54 | 55 | [JsonProperty("args")] 56 | public string Args { get; set; } = string.Empty; 57 | } 58 | 59 | internal partial class PlatformSettings 60 | { 61 | [JsonProperty("os")] 62 | public string Os { get; set; } = string.Empty; 63 | 64 | [JsonProperty("osVersion")] 65 | public string OsVersion { get; set; } = string.Empty; 66 | 67 | [JsonProperty("arch")] 68 | public string Architecture { get; set; } = string.Empty; 69 | } 70 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/CommandHelper.cs: -------------------------------------------------------------------------------- 1 | using Valleysoft.DockerRegistryClient; 2 | using Valleysoft.DockerRegistryClient.Models; 3 | 4 | namespace Valleysoft.Dredge; 5 | 6 | internal static class CommandHelper 7 | { 8 | public static async Task ExecuteCommandAsync(string? registry, Func execute) 9 | { 10 | try 11 | { 12 | await execute(); 13 | } 14 | catch (Exception e) 15 | { 16 | ConsoleColor savedColor = Console.ForegroundColor; 17 | Console.ForegroundColor = ConsoleColor.Red; 18 | 19 | string message = e.Message; 20 | if (e is RegistryException dockerRegistryException) 21 | { 22 | Error? error = dockerRegistryException.Errors.FirstOrDefault(); 23 | if (error?.Code == "UNAUTHORIZED") 24 | { 25 | string loginCommand = "docker login"; 26 | if (registry is not null) 27 | { 28 | loginCommand += $" {registry}"; 29 | } 30 | 31 | message = $"The repository does not exist or may require authentication. If authentication is required, ensure that your credentials are stored for the registry by running '{loginCommand}'."; 32 | } 33 | else 34 | { 35 | message = error?.Message ?? message; 36 | } 37 | } 38 | 39 | Console.Error.WriteLine(message); 40 | Console.ForegroundColor = savedColor; 41 | Environment.Exit(1); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/CommandWithOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Invocation; 3 | 4 | namespace Valleysoft.Dredge.Commands; 5 | 6 | public abstract class CommandWithOptions : Command 7 | where TOptions : OptionsBase, new() 8 | { 9 | public new TOptions Options { get; set; } = new(); 10 | 11 | protected CommandWithOptions(string name, string description) 12 | : base(name, description) 13 | { 14 | Options.SetCommandOptions(this); 15 | this.SetHandler(ExecuteAsyncCore); 16 | } 17 | 18 | private async Task ExecuteAsyncCore(InvocationContext context) 19 | { 20 | Options.SetParseResult(context.BindingContext.ParseResult); 21 | await ExecuteAsync(); 22 | } 23 | 24 | protected abstract Task ExecuteAsync(); 25 | } 26 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/CompareCommand.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class CompareCommand : Command 6 | { 7 | public CompareCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 8 | : base("compare", "Compares two images") 9 | { 10 | AddCommand(new CompareLayersCommand(dockerRegistryClientFactory)); 11 | AddCommand(new CompareFilesCommand(dockerRegistryClientFactory)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/CompareFilesCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class CompareFilesCommand : RegistryCommandBase 6 | { 7 | private const string BaseOutputDirName = "base"; 8 | private const string TargetOutputDirName = "target"; 9 | 10 | private static readonly string CompareTempPath = Path.Combine(DredgeState.DredgeTempPath, "compare"); 11 | 12 | public CompareFilesCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 13 | : base("files", "Compares two images by their files", dockerRegistryClientFactory) 14 | { 15 | } 16 | 17 | protected override Task ExecuteAsync() 18 | { 19 | return CommandHelper.ExecuteCommandAsync(registry: null, async () => 20 | { 21 | AppSettings settings = AppSettings.Load(); 22 | if (settings.FileCompareTool is null || 23 | settings.FileCompareTool.ExePath == string.Empty || 24 | settings.FileCompareTool.Args == string.Empty) 25 | { 26 | throw new Exception( 27 | $"This command requires additional configuration.{Environment.NewLine}In order to compare files, you must first set the '{AppSettings.FileCompareToolName}' setting in {AppSettings.SettingsPath}. This is an external tool of your choosing that will be executed to compare two directories containing files of the specified images. Use '{{0}}' and '{{1}}' placeholders in the args to indicate the base and target path locations that will be the inputs to the compare tool."); 28 | } 29 | 30 | await SaveImageLayersToDiskAsync(Options.BaseImage, BaseOutputDirName, Options.BaseLayerIndex, CompareOptionsBase.BaseArg); 31 | Console.Error.WriteLine(); 32 | await SaveImageLayersToDiskAsync(Options.TargetImage, TargetOutputDirName, Options.TargetLayerIndex, CompareOptionsBase.TargetArg); 33 | 34 | string args = settings.FileCompareTool.Args 35 | .Replace("{0}", Path.Combine(CompareTempPath, BaseOutputDirName)) 36 | .Replace("{1}", Path.Combine(CompareTempPath, TargetOutputDirName)); 37 | Process.Start(settings.FileCompareTool.ExePath, args); 38 | }); 39 | } 40 | 41 | private Task SaveImageLayersToDiskAsync(string image, string outputDirName, int? layerIndex, string layerIndexArg) 42 | { 43 | string workingDir = Path.Combine(CompareTempPath, outputDirName); 44 | if (Directory.Exists(workingDir)) 45 | { 46 | Directory.Delete(workingDir, recursive: true); 47 | } 48 | 49 | return ImageHelper.SaveImageLayersToDiskAsync( 50 | DockerRegistryClientFactory, 51 | image, 52 | workingDir, 53 | layerIndex, 54 | layerIndexArg + CompareFilesOptions.LayerIndexSuffix, 55 | noSquash: false, 56 | Options); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/CompareFilesOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class CompareFilesOptions : CompareOptionsBase 6 | { 7 | public const string LayerIndexSuffix = "-layer-index"; 8 | 9 | private readonly Option baseLayerIndex; 10 | private readonly Option targetLayerIndex; 11 | private readonly Option outputOption; 12 | 13 | public int? BaseLayerIndex { get; set; } 14 | public int? TargetLayerIndex { get; set; } 15 | public CompareFilesOutput OutputType { get; set; } 16 | 17 | public CompareFilesOptions() 18 | { 19 | baseLayerIndex = Add(new Option($"--{BaseArg}{LayerIndexSuffix}", "Non-empty layer index of the base container image to compare with")); 20 | targetLayerIndex = Add(new Option($"--{TargetArg}{LayerIndexSuffix}", "Non-empty layer index of the target container image to compare against")); 21 | outputOption = Add(new Option("--output", () => CompareFilesOutput.ExternalTool, "Output type")); 22 | } 23 | 24 | protected override void GetValues() 25 | { 26 | base.GetValues(); 27 | BaseLayerIndex = GetValue(baseLayerIndex); 28 | TargetLayerIndex = GetValue(targetLayerIndex); 29 | OutputType = GetValue(outputOption); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/CompareLayersOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class CompareLayersOptions : CompareOptionsBase 6 | { 7 | private readonly Option outputOption; 8 | private readonly Option noColorOption; 9 | private readonly Option historyOption; 10 | private readonly Option compressedSizeOption; 11 | 12 | public CompareLayersOutput OutputFormat { get; set; } 13 | public bool IsColorDisabled { get; set; } 14 | public bool IncludeHistory { get; set; } 15 | public bool IncludeCompressedSize { get; set; } 16 | 17 | public CompareLayersOptions() 18 | { 19 | outputOption = Add(new Option("--output", () => CompareLayersOutput.SideBySide, "Output format")); 20 | noColorOption = Add(new Option("--no-color", "Disables dependency on color in comparison results")); 21 | historyOption = Add(new Option("--history", "Include layer history as part of the comparison")); 22 | compressedSizeOption = Add(new Option("--compressed-size", "Show the compressed size of the layer")); 23 | } 24 | 25 | protected override void GetValues() 26 | { 27 | base.GetValues(); 28 | OutputFormat = GetValue(outputOption); 29 | IsColorDisabled = GetValue(noColorOption); 30 | IncludeHistory = GetValue(historyOption); 31 | IncludeCompressedSize = GetValue(compressedSizeOption); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/CompareOptionsBase.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class CompareOptionsBase : PlatformOptionsBase 6 | { 7 | public const string BaseArg = "base"; 8 | public const string TargetArg = "target"; 9 | 10 | private readonly Argument baseImageArg; 11 | private readonly Argument targetImageArg; 12 | 13 | public string BaseImage { get; set; } = string.Empty; 14 | public string TargetImage { get; set; } = string.Empty; 15 | 16 | public CompareOptionsBase() 17 | { 18 | baseImageArg = Add(new Argument(BaseArg, "Name of the base container image (, :, or @)")); 19 | targetImageArg = Add(new Argument(TargetArg, "Name of the target container image (, :, or @)")); 20 | } 21 | 22 | protected override void GetValues() 23 | { 24 | base.GetValues(); 25 | BaseImage = GetValue(baseImageArg); 26 | TargetImage = GetValue(targetImageArg); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/DockerfileOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class DockerfileOptions : PlatformOptionsBase 6 | { 7 | private readonly Argument imageArg; 8 | private readonly Option noColorOption; 9 | private readonly Option noFormatOption; 10 | 11 | public string Image { get; set; } = string.Empty; 12 | public bool NoColor { get; set; } 13 | public bool NoFormat { get; set; } 14 | 15 | public DockerfileOptions() 16 | { 17 | imageArg = Add(new Argument("image", "Name of the container image (, :, or @)")); 18 | noColorOption = Add(new Option("--no-color", "Disables use of syntax color in the output")); 19 | noFormatOption = Add(new Option("--no-format", "Disables use of heuristics to format the layer history for better readability")); 20 | } 21 | 22 | protected override void GetValues() 23 | { 24 | base.GetValues(); 25 | Image = GetValue(imageArg); 26 | NoColor = GetValue(noColorOption); 27 | NoFormat = GetValue(noFormatOption); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/ImageCommand.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class ImageCommand : Command 6 | { 7 | public ImageCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 8 | : base("image", "Commands related to container images") 9 | { 10 | AddCommand(new CompareCommand(dockerRegistryClientFactory)); 11 | AddCommand(new InspectCommand(dockerRegistryClientFactory)); 12 | AddCommand(new OsCommand(dockerRegistryClientFactory)); 13 | AddCommand(new SaveLayersCommand(dockerRegistryClientFactory)); 14 | AddCommand(new DockerfileCommand(dockerRegistryClientFactory)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/InspectCommand.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Valleysoft.DockerRegistryClient.Models.Manifests; 3 | 4 | namespace Valleysoft.Dredge.Commands.Image; 5 | 6 | public class InspectCommand : RegistryCommandBase 7 | { 8 | public InspectCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 9 | : base("inspect", "Return low-level information on a container image", dockerRegistryClientFactory) 10 | { 11 | } 12 | 13 | protected override Task ExecuteAsync() 14 | { 15 | ImageName imageName = ImageName.Parse(Options.Image); 16 | return CommandHelper.ExecuteCommandAsync(imageName.Registry, async () => 17 | { 18 | using IDockerRegistryClient client = await DockerRegistryClientFactory.GetClientAsync(imageName.Registry); 19 | IImageManifest manifest = (await ManifestHelper.GetResolvedManifestAsync(client, imageName, Options)).Manifest; 20 | string? digest = (manifest.Config?.Digest) ?? 21 | throw new NotSupportedException($"Could not resolve the image config digest of '{Options.Image}'."); 22 | Stream blob = await client.Blobs.GetAsync(imageName.Repo, digest); 23 | using StreamReader reader = new(blob); 24 | string content = await reader.ReadToEndAsync(); 25 | object? json = JsonConvert.DeserializeObject(content) ?? 26 | throw new Exception($"Unable to deserialize content into JSON:\n{content}"); 27 | string output = JsonConvert.SerializeObject(json, JsonHelper.Settings); 28 | Console.Out.WriteLine(output); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/InspectOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class InspectOptions : PlatformOptionsBase 6 | { 7 | private readonly Argument imageArg; 8 | 9 | public string Image { get; set; } = string.Empty; 10 | 11 | public InspectOptions() 12 | { 13 | imageArg = Add(new Argument("image", "Name of the container image (, :, or @)")); 14 | } 15 | 16 | protected override void GetValues() 17 | { 18 | base.GetValues(); 19 | Image = GetValue(imageArg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/OsCommand.cs: -------------------------------------------------------------------------------- 1 | using ICSharpCode.SharpZipLib.Tar; 2 | using Newtonsoft.Json; 3 | using System.IO.Compression; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using Valleysoft.DockerRegistryClient; 7 | using Valleysoft.DockerRegistryClient.Models.Manifests; 8 | using ImageConfig = Valleysoft.DockerRegistryClient.Models.Images.Image; 9 | 10 | namespace Valleysoft.Dredge.Commands.Image; 11 | 12 | public partial class OsCommand : RegistryCommandBase 13 | { 14 | private static readonly Regex osReleaseRegex = OsReleaseRegex(); 15 | 16 | public OsCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 17 | : base("os", "Gets OS info about the container image", dockerRegistryClientFactory) 18 | { 19 | } 20 | 21 | protected override Task ExecuteAsync() 22 | { 23 | ImageName imageName = ImageName.Parse(Options.Image); 24 | return CommandHelper.ExecuteCommandAsync(imageName.Registry, async () => 25 | { 26 | using IDockerRegistryClient client = await DockerRegistryClientFactory.GetClientAsync(imageName.Registry); 27 | IImageManifest manifest = (await ManifestHelper.GetResolvedManifestAsync(client, imageName, Options)).Manifest; 28 | 29 | string? configDigest = (manifest.Config?.Digest) ?? throw new NotSupportedException($"Could not resolve the image config digest of '{Options.Image}'."); 30 | ImageConfig imageConfig = await client.Blobs.GetImageAsync(imageName.Repo, configDigest); 31 | 32 | IDescriptor baseLayer = manifest.Layers.First(); 33 | if (baseLayer.Digest is null) 34 | { 35 | throw new Exception($"No digest was found for the base layer of '{Options.Image}'."); 36 | } 37 | 38 | object? osInfo; 39 | if (imageConfig.Os.Equals("windows", StringComparison.OrdinalIgnoreCase)) 40 | { 41 | var windowsOsInfo = await GetWindowsOsInfoAsync(imageConfig, baseLayer.Digest, DockerRegistryClientFactory); 42 | osInfo = windowsOsInfo?.Info; 43 | } 44 | else 45 | { 46 | osInfo = await GetLinuxOsInfoAsync(client, imageName, baseLayer.Digest); 47 | } 48 | 49 | if (osInfo is null) 50 | { 51 | throw new Exception("Unable to derive OS information from the image."); 52 | } 53 | 54 | string output = JsonConvert.SerializeObject(osInfo, JsonHelper.SettingsNoCamelCase); 55 | Console.Out.WriteLine(output); 56 | }); 57 | } 58 | 59 | private static async Task GetLinuxOsInfoAsync(IDockerRegistryClient client, ImageName imageName, string baseLayerDigest) 60 | { 61 | using Stream blobStream = await client.Blobs.GetAsync(imageName.Repo, baseLayerDigest); 62 | using GZipStream gZipStream = new(blobStream, CompressionMode.Decompress); 63 | 64 | // Can't use System.Formats.Tar.TarReader because it fails to read certain types of tarballs: 65 | // https://github.com/dotnet/runtime/issues/74316#issuecomment-1312227247 66 | 67 | using TarInputStream tarStream = new(gZipStream, Encoding.UTF8); 68 | TarEntry? entry = null; 69 | do 70 | { 71 | entry = tarStream.GetNextEntry(); 72 | 73 | // Look for the os-release file (skip symlinks) 74 | if (entry is not null && 75 | entry.Size > 0 && 76 | (osReleaseRegex.IsMatch(entry.Name))) 77 | { 78 | using MemoryStream memStream = new(); 79 | tarStream.CopyEntryContents(memStream); 80 | memStream.Position = 0; 81 | using StreamReader reader = new(memStream); 82 | string content = await reader.ReadToEndAsync(); 83 | return LinuxOsInfo.Parse(content); 84 | } 85 | } while (entry is not null); 86 | 87 | return null; 88 | } 89 | 90 | internal static async Task<(WindowsOsInfo Info, string Repo)?> GetWindowsOsInfoAsync(ImageConfig imageConfig, string baseLayerDigest, 91 | IDockerRegistryClientFactory dockerRegistryClientFactory) 92 | { 93 | const string NanoServerRepo = "windows/nanoserver"; 94 | const string ServerCoreRepo = "windows/servercore"; 95 | const string ServerRepo = "windows/server"; 96 | const string WindowsRepo = "windows"; 97 | 98 | using IDockerRegistryClient mcrClient = 99 | await dockerRegistryClientFactory.GetClientAsync(RegistryHelper.McrRegistry); 100 | 101 | if (await mcrClient.Blobs.ExistsAsync(NanoServerRepo, baseLayerDigest)) 102 | { 103 | return (new(WindowsType.NanoServer, imageConfig.OsVersion), NanoServerRepo); 104 | } 105 | else if (await mcrClient.Blobs.ExistsAsync(ServerCoreRepo, baseLayerDigest)) 106 | { 107 | return (new(WindowsType.ServerCore, imageConfig.OsVersion), ServerCoreRepo); 108 | } 109 | else if (await mcrClient.Blobs.ExistsAsync(ServerRepo, baseLayerDigest)) 110 | { 111 | return (new(WindowsType.Server, imageConfig.OsVersion), ServerRepo); 112 | } 113 | else if (await mcrClient.Blobs.ExistsAsync(WindowsRepo, baseLayerDigest)) 114 | { 115 | return (new(WindowsType.Windows, imageConfig.OsVersion), WindowsRepo); 116 | } 117 | else 118 | { 119 | return null; 120 | } 121 | } 122 | 123 | [GeneratedRegex(@"(\./)?(etc|usr/lib)/os-release")] 124 | private static partial Regex OsReleaseRegex(); 125 | } 126 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/OsOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class OsOptions : PlatformOptionsBase 6 | { 7 | private readonly Argument imageArg; 8 | 9 | public string Image { get; set; } = string.Empty; 10 | 11 | public OsOptions() 12 | { 13 | imageArg = Add(new Argument("image", "Name of the container image (, :, or @)")); 14 | } 15 | 16 | protected override void GetValues() 17 | { 18 | base.GetValues(); 19 | Image = GetValue(imageArg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/SaveLayersCommand.cs: -------------------------------------------------------------------------------- 1 | using Valleysoft.DockerRegistryClient.Models.Manifests; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class SaveLayersCommand : RegistryCommandBase 6 | { 7 | public SaveLayersCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 8 | : base("save-layers", "Saves an image's extracted layers to disk", dockerRegistryClientFactory) 9 | { 10 | } 11 | 12 | protected override Task ExecuteAsync() 13 | { 14 | ImageName imageName = ImageName.Parse(Options.Image); 15 | return CommandHelper.ExecuteCommandAsync(imageName.Registry, async () => 16 | { 17 | using IDockerRegistryClient client = await DockerRegistryClientFactory.GetClientAsync(imageName.Registry); 18 | IImageManifest manifest = (await ManifestHelper.GetResolvedManifestAsync(client, imageName, Options)).Manifest; 19 | string? digest = (manifest.Config?.Digest) ?? throw new NotSupportedException($"Could not resolve the image config digest of '{Options.Image}'."); 20 | await ImageHelper.SaveImageLayersToDiskAsync( 21 | DockerRegistryClientFactory, 22 | Options.Image, 23 | Options.OutputPath, 24 | Options.LayerIndex, 25 | SaveLayersOptions.LayerIndexOptionName, 26 | Options.NoSquash, 27 | Options); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Image/SaveLayersOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Image; 4 | 5 | public class SaveLayersOptions : PlatformOptionsBase 6 | { 7 | public const string LayerIndexOptionName = "--layer-index"; 8 | 9 | private readonly Argument imageArg; 10 | private readonly Argument outputPathArg; 11 | private readonly Option noSquashOption; 12 | private readonly Option layerIndexOption; 13 | 14 | public string Image { get; set; } = string.Empty; 15 | public string OutputPath { get; set; } = string.Empty; 16 | public bool NoSquash { get; set; } 17 | public int? LayerIndex { get; set; } 18 | 19 | public SaveLayersOptions() 20 | { 21 | imageArg = Add(new Argument("image", "Name of the container image (, :, or @)")); 22 | outputPathArg = Add(new Argument("output-path", "Path to the output location")); 23 | noSquashOption = Add(new Option("--no-squash", "Do not squash the image layers")); 24 | layerIndexOption = Add(new Option(LayerIndexOptionName, "Index of the image layer to target")); 25 | } 26 | 27 | protected override void GetValues() 28 | { 29 | base.GetValues(); 30 | Image = GetValue(imageArg); 31 | OutputPath = GetValue(outputPathArg); 32 | NoSquash = GetValue(noSquashOption); 33 | LayerIndex = GetValue(layerIndexOption); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/DigestCommand.cs: -------------------------------------------------------------------------------- 1 | using Valleysoft.DockerRegistryClient; 2 | 3 | namespace Valleysoft.Dredge.Commands.Manifest; 4 | 5 | public class DigestCommand : RegistryCommandBase 6 | { 7 | public DigestCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 8 | : base("digest", "Queries the digest of a manifest", dockerRegistryClientFactory) 9 | { 10 | } 11 | 12 | protected override Task ExecuteAsync() 13 | { 14 | ImageName imageName = ImageName.Parse(Options.Image); 15 | return CommandHelper.ExecuteCommandAsync(imageName.Registry, async () => 16 | { 17 | using IDockerRegistryClient client = await DockerRegistryClientFactory.GetClientAsync(imageName.Registry); 18 | 19 | string digest = await client.Manifests.GetDigestAsync(imageName.Repo, (imageName.Tag ?? imageName.Digest)!); 20 | 21 | Console.Out.WriteLine(digest); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/DigestOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Manifest; 4 | 5 | public class DigestOptions : OptionsBase 6 | { 7 | private readonly Argument imageArg; 8 | 9 | public string Image { get; set; } = string.Empty; 10 | 11 | public DigestOptions() 12 | { 13 | imageArg = Add(new Argument("name", "Name of the manifest (, :, or @)")); 14 | } 15 | 16 | protected override void GetValues() 17 | { 18 | Image = GetValue(imageArg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/GetCommand.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Valleysoft.DockerRegistryClient.Models.Manifests; 3 | 4 | namespace Valleysoft.Dredge.Commands.Manifest; 5 | 6 | public class GetCommand : RegistryCommandBase 7 | { 8 | public GetCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 9 | : base("get", "Queries a manifest", dockerRegistryClientFactory) 10 | { 11 | } 12 | 13 | protected override Task ExecuteAsync() 14 | { 15 | ImageName imageName = ImageName.Parse(Options.Image); 16 | return CommandHelper.ExecuteCommandAsync(imageName.Registry, async () => 17 | { 18 | using IDockerRegistryClient client = await DockerRegistryClientFactory.GetClientAsync(imageName.Registry); 19 | 20 | ManifestInfo manifestInfo = await client.Manifests.GetAsync(imageName.Repo, (imageName.Tag ?? imageName.Digest)!); 21 | 22 | string output = JsonConvert.SerializeObject(manifestInfo.Manifest, JsonHelper.Settings); 23 | 24 | Console.Out.WriteLine(output); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/GetOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Manifest; 4 | 5 | public class GetOptions : OptionsBase 6 | { 7 | private readonly Argument imageArg; 8 | 9 | public string Image { get; set; } = string.Empty; 10 | 11 | public GetOptions() 12 | { 13 | imageArg = Add(new Argument("name", "Name of the manifest (, :, or @)")); 14 | } 15 | 16 | protected override void GetValues() 17 | { 18 | Image = GetValue(imageArg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/ManifestCommand.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Manifest; 4 | 5 | public class ManifestCommand : Command 6 | { 7 | public ManifestCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) : base("manifest", "Commands related to manifests") 8 | { 9 | AddCommand(new GetCommand(dockerRegistryClientFactory)); 10 | AddCommand(new DigestCommand(dockerRegistryClientFactory)); 11 | AddCommand(new ResolveCommand(dockerRegistryClientFactory)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/ResolveCommand.cs: -------------------------------------------------------------------------------- 1 | using Valleysoft.DockerRegistryClient.Models.Manifests; 2 | 3 | namespace Valleysoft.Dredge.Commands.Manifest; 4 | 5 | public class ResolveCommand : RegistryCommandBase 6 | { 7 | public ResolveCommand(IDockerRegistryClientFactory dockerRegistryClientFactory) 8 | : base("resolve", "Resolves a manifest to a target platform's fully-qualified image digest", dockerRegistryClientFactory) 9 | { 10 | } 11 | 12 | protected override Task ExecuteAsync() 13 | { 14 | ImageName imageName = ImageName.Parse(Options.Image); 15 | return CommandHelper.ExecuteCommandAsync(imageName.Registry, async () => 16 | { 17 | using IDockerRegistryClient client = await DockerRegistryClientFactory.GetClientAsync(imageName.Registry); 18 | ManifestInfo manifestInfo = (await ManifestHelper.GetResolvedManifestAsync(client, imageName, Options)).ManifestInfo; 19 | ImageName fullyQualifiedDigest = new(imageName.Registry, imageName.Repo, tag: null, manifestInfo.DockerContentDigest); 20 | 21 | Console.Out.WriteLine(fullyQualifiedDigest.ToString()); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/Manifest/ResolveOptions.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace Valleysoft.Dredge.Commands.Manifest; 4 | 5 | public class SetOptions : PlatformOptionsBase 6 | { 7 | private readonly Argument imageArg; 8 | 9 | public string Image { get; set; } = string.Empty; 10 | 11 | public SetOptions() 12 | { 13 | imageArg = Add(new Argument("image", "Name of the container image (, :, or @)")); 14 | } 15 | 16 | protected override void GetValues() 17 | { 18 | base.GetValues(); 19 | Image = GetValue(imageArg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Valleysoft.Dredge/Commands/OptionsBase.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Parsing; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace Valleysoft.Dredge.Commands; 6 | 7 | public abstract class OptionsBase 8 | { 9 | private readonly List arguments = []; 10 | private readonly List