From 683d2546bdce88944ff36928a888b18f95817bc0 Mon Sep 17 00:00:00 2001 From: Al Berez Date: Thu, 6 Mar 2025 09:04:15 -0800 Subject: [PATCH 1/3] Test legacy ubuntu wrapper --- .github/workflows/release-bump-gpg.yml | 131 ++----------------------- 1 file changed, 9 insertions(+), 122 deletions(-) diff --git a/.github/workflows/release-bump-gpg.yml b/.github/workflows/release-bump-gpg.yml index fcb46f0c75a..c92d01f61bd 100644 --- a/.github/workflows/release-bump-gpg.yml +++ b/.github/workflows/release-bump-gpg.yml @@ -1,68 +1,7 @@ -# # Manual steps to bump and save GPG key -# -# ## Log in on GHA worker following upterm job instructions -# -# GPG key should be loaded in this session by previous steps -# -# ## Login to gh tool -# -# This is required to save GHA secrets. Your GitHub user should have admin:repo -# permissions -# -# - `gh auth login` - hit enter; it will open browser -# - `echo "All future steps will be applied to: ${GITHUB_REPOSITORY:?}"` -# - `gh secret list -R ${GITHUB_REPOSITORY:?}` -# -# ## Backup previous working GPG key -# -# DO THIS STEP ONLY WHEN YOU KNOW THAT SIGNING_KEY_GPG KEY IS WORKING -# -# - `gh secret set BACKUP_SIGNING_KEY_GPG -R ${GITHUB_REPOSITORY:?} -b"${SIGNING_KEY_GPG:?}"` -# -# ## Update GPG key expiration date -# -# - `gpg --list-keys` -# - `echo "GPG ID: ${SIGNING_KEY_GPG_ID:?}"` -# - `echo "GPG Passphrase: ${SIGNING_KEY_GPG_PASSPHRASE:?}"` -# -# - `gpg --edit-key "${SIGNING_KEY_GPG_ID}"` -# - Inside the gpg tool -# - `list` -# - `key 0` - to select private key -# - `expire` -# - `1y` - to set to 1 year from now -# - `key 1` - to select private key -# - `expire` -# - `1y` - to set to 1 year from now -# - `list` check expiration dates -# - `save` - this will save updated keys to GPG keyring -# -# - `gpg --list-keys` - check new expiration dates on both public and private keys -# -# ## Export keys from the keyring and save them to GigHub Actions secrets -# -# - `echo "GPG Passphrase: ${SIGNING_KEY_GPG_PASSPHRASE:?}"` -# -# - `gpg --armor --export "${SIGNING_KEY_GPG_ID:?}"` - we need this public key to update CLAW -# -# - `gpg --export-secret-key "${SIGNING_KEY_GPG_ID:?}" | base64 | gh secret set SIGNING_KEY_GPG -R ${GITHUB_REPOSITORY:?}` -# -# to keep GPG Passphrase UI without distortion use snippet below instead of the top one -# ``` -# key_pvt="$(gpg --export-secret-key "${SIGNING_KEY_GPG_ID:?}" | base64)" -# gh secret set SIGNING_KEY_GPG2 -R ${GITHUB_REPOSITORY:?} -b"${key_pvt}" -# ``` -# -# List of GHA secrets: -# SIGNING_KEY_GPG -# SIGNING_KEY_GPG_ID -# SIGNING_KEY_GPG_PASSPHRASE - -name: 'Release: Bump GPG' +name: Test legacy ubuntu container wrapper on: workflow_dispatch: - inputs: permissions: contents: write @@ -72,65 +11,13 @@ defaults: shell: bash jobs: - setup: - name: Setup - runs-on: ubuntu-latest - if: ${{ github.action_repository != 'cloudfoundry/cli' }} - outputs: - build-version: ${{ steps.set-build-version.outputs.build-version }} - secrets-environment: ${{ steps.set-secrets-environment.outputs.secrets-environment }} - steps: - - - name: Set environment - id: set-secrets-environment - run: echo "::set-output name=secrets-environment::PROD" - - bump-gpg: - name: Bump GPG - needs: - - setup - runs-on: ubuntu-latest - environment: ${{ needs.setup.outputs.secrets-environment }} - - steps: - - - name: Load GPG key - env: - SIGNING_KEY_GPG: ${{ secrets.SIGNING_KEY_GPG }} - run: echo -n "${SIGNING_KEY_GPG:?}" | base64 --decode | gpg --no-tty --batch --pinentry-mode loopback --import - - - name: View GPG keys - run: gpg --list-keys - - - name: Setup upterm session - env: - BACKUP_SIGNING_KEY_GPG: ${{ secrets.BACKUP_SIGNING_KEY_GPG }} - SIGNING_KEY_GPG: ${{ secrets.SIGNING_KEY_GPG }} - SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} - SIGNING_KEY_GPG_PASSPHRASE: ${{ secrets.SIGNING_KEY_GPG_PASSPHRASE }} - if: always() - uses: lhotari/action-upterm@v1 - timeout-minutes: 60 - - - name: Print public key to update CLAW - env: - SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} - run: gpg --armor --export "${SIGNING_KEY_GPG_ID:?}" - - verify-gpg: - name: Verify GPG - needs: - - setup - - bump-gpg + test-container-wrapper: runs-on: ubuntu-latest - environment: ${{ needs.setup.outputs.secrets-environment }} - steps: - - - name: Load GPG key - env: - SIGNING_KEY_GPG: ${{ secrets.SIGNING_KEY_GPG }} - run: echo -n "${SIGNING_KEY_GPG:?}" | base64 --decode | gpg --no-tty --batch --pinentry-mode loopback --import - - - name: View GPG keys - run: gpg --list-keys + - name: Latest Ubuntu + run: cat /etc/os-release + - name: Ubuntu 20.04 + run: | + docker run -i --rm -v ${{ github.workspace }}:/workspace -w /workspace ubuntu:20.04 << EOM + cat /etc/os-release + EOM \ No newline at end of file From ca188c44f0a23fcdee4f2454fab41460d3f55d0a Mon Sep 17 00:00:00 2001 From: Al Berez Date: Thu, 6 Mar 2025 10:34:09 -0800 Subject: [PATCH 2/3] port safe build workflow --- .github/workflows/release-bump-gpg.yml | 374 ++++++++++++++++++++++++- 1 file changed, 364 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release-bump-gpg.yml b/.github/workflows/release-bump-gpg.yml index c92d01f61bd..e5ce7ecfb69 100644 --- a/.github/workflows/release-bump-gpg.yml +++ b/.github/workflows/release-bump-gpg.yml @@ -2,22 +2,376 @@ name: Test legacy ubuntu container wrapper on: workflow_dispatch: - -permissions: - contents: write + inputs: + release_version: + description: 'Release version bump' + required: true + default: 'patch' + type: choice + options: + - minor + - patch defaults: run: shell: bash jobs: - test-container-wrapper: + setup: + name: Setup runs-on: ubuntu-latest + env: + VERSION_MAJOR: 8 + outputs: + aws-s3-bucket: "v${{ steps.bump-version.outputs.version-major }}-cf-cli-releases" + + version-build: ${{ steps.bump-version.outputs.version-build }} + version-major: ${{ env.VERSION_MAJOR }} + version-minor: ${{ steps.bump-version.outputs.version-minor }} + version-patch: ${{ steps.bump-version.outputs.version-patch }} + steps: - - name: Latest Ubuntu - run: cat /etc/os-release - - name: Ubuntu 20.04 + - name: Checkout cli + uses: actions/checkout@v4 + + - name: Bump version + id: bump-version + run: | + set -x + git fetch --tags --quiet + latest_tag="$(git tag | sort -V | grep v${VERSION_MAJOR} | tail -1)" + echo "Latest tag is ${latest_tag}" + + version="${latest_tag#[vV]}" + + version_minor="${version#*.}" + version_minor="${version_minor%.*}" + version_patch=${version##*.} + + if [ "${{ inputs.release_version }}" == "minor" ]; then + version_minor=$(($version_minor + 1)) + version_patch=0 + else + version_patch=$(($version_patch + 1)) + fi + + new_version="${VERSION_MAJOR}.${version_minor}.${version_patch}" + echo "new version is ${new_version}" + + echo "version-build=${new_version}" >> "${GITHUB_OUTPUT}" + echo "version-minor=${version_minor}" >> "${GITHUB_OUTPUT}" + echo "version-patch=${version_patch}" >> "${GITHUB_OUTPUT}" + + + build-linux: + name: Build Linux + needs: + - setup + runs-on: ubuntu-latest + + env: + VERSION_BUILD: ${{ needs.setup.outputs.version-build }} + VERSION_MAJOR: ${{ needs.setup.outputs.version-major }} + + steps: + + - name: Get Build Version + id: get_build_version + run: echo "VERSION_BUILD $VERSION_BUILD" + + - name: Checkout cli + uses: actions/checkout@v4 + + - name: Checkout cli-ci + uses: actions/checkout@v4 + with: + repository: cloudfoundry/cli-ci.git + path: cli-ci + ref: main + + - name: Install Linux Packages + run: sudo apt update && sudo apt install -y --no-install-recommends fakeroot + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Print go environment + id: go-cache-paths + run: | + echo "go-build=$(go env GOCACHE)" >> "${GITHUB_OUTPUT}" + echo "go-mod=$(go env GOMODCACHE)" >> "${GITHUB_OUTPUT}" + go env + + - name: Go Assets Cache + uses: actions/cache@v4 + with: + path: | + ${{ steps.go-cache-paths.outputs.go-mod }} + ${{ steps.go-cache-paths.outputs.go-build }} + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Build Linux + env: + CF_BUILD_VERSION: ${VERSION_BUILD} + run: | + make out/cf-cli_linux_i686 + make out/cf-cli_linux_x86-64 + make out/cf-cli_linux_arm64 + + - name: Store Linux Binaries + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: cf-cli-linux-binaries + path: out/cf-cli_linux* + + - name: Build RedHat Packages + env: + SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} + run: | + set -ex + set -o pipefail + + root=$PWD + + cat<< EOF >~/.rpmmacros + $SIGNING_KEY_GPG_ID + EOF + + RPM_VERSION=${VERSION_BUILD//-/_} + + mkdir -pv $root/packaged + + echo "Build 32-bit RedHat package" + ( + pushd cli-ci/ci/installers/rpm + cp $root/out/cf-cli_linux_i686 cf${VERSION_MAJOR} + cp ../../license/NOTICE . + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES LICENSE + cp ../completion/cf${VERSION_MAJOR} cf${VERSION_MAJOR}.bash + echo "Version: ${RPM_VERSION}" > cf-cli.spec + cat cf${VERSION_MAJOR}-cli.spec.template >> cf-cli.spec + rpmbuild --target i386 --define "_topdir $(pwd)/build" -bb cf-cli.spec + mv build/RPMS/i386/cf${VERSION_MAJOR}-cli*.rpm $root/packaged/cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_i686.rpm + popd + ) + + echo "Build 64-bit RedHat package" + ( + pushd cli-ci/ci/installers/rpm + cp $root/out/cf-cli_linux_x86-64 cf${VERSION_MAJOR} + cp ../../license/NOTICE . + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES LICENSE + cp ../completion/cf${VERSION_MAJOR} cf${VERSION_MAJOR}.bash + echo "Version: ${RPM_VERSION}" > cf-cli.spec + cat cf${VERSION_MAJOR}-cli.spec.template >> cf-cli.spec + rpmbuild --target x86_64 --define "_topdir $(pwd)/build" -bb cf-cli.spec + mv build/RPMS/x86_64/cf${VERSION_MAJOR}-cli*.rpm $root/packaged/cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_x86-64.rpm + popd + ) + + echo "Build arm64 RedHat package" + ( + pushd cli-ci/ci/installers/rpm + cp $root/out/cf-cli_linux_arm64 cf${VERSION_MAJOR} + cp ../../license/NOTICE . + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES LICENSE + cp ../completion/cf${VERSION_MAJOR} cf${VERSION_MAJOR}.bash + echo "Version: ${RPM_VERSION}" > cf-cli.spec + cat cf${VERSION_MAJOR}-cli.spec.template >> cf-cli.spec + rpmbuild --target aarch64 --define "_topdir $(pwd)/build" -bb cf-cli.spec + mv build/RPMS/aarch64/cf${VERSION_MAJOR}-cli*.rpm $root/packaged/cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_aarch64.rpm + popd + ) + + - name: Load GPG key + env: + SIGNING_KEY_GPG: ${{ secrets.SIGNING_KEY_GPG }} + run: | + echo -n "$SIGNING_KEY_GPG" | base64 --decode | gpg --no-tty --batch --pinentry-mode loopback --import + + - name: View GPG keys + run: | + gpg --list-keys + + - name: Sign RedHat Packages + env: + SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} + SIGNING_KEY_GPG_PASSPHRASE: ${{ secrets.SIGNING_KEY_GPG_PASSPHRASE }} + run: | + set -ex + set -o pipefail + + mkdir signed-redhat-installer + + cat<< EOF >~/.rpmmacros + %_signature gpg + %_gpg_name $SIGNING_KEY_GPG_ID + %_gpgbin /usr/bin/gpg2 + %__gpg_sign_cmd %{__gpg} gpg --force-v3-sigs --batch --verbose --no-armor \ + --passphrase "$SIGNING_KEY_GPG_PASSPHRASE" --no-secmem-warning -u "%{_gpg_name}" \ + -sbo %{__signature_filename} --digest-algo sha256 %{__plaintext_filename} + EOF + + cp packaged/cf*.rpm signed-redhat-installer/ + + #TODO: consider to add --key-id + #TODO: DEV shim + rpmsign --addsign signed-redhat-installer/*.rpm + + - name: Print RPM Signature + run: rpm -q --qf 'FN:\t%{FILENAMES}\nNAME:\t%{NAME}\nPGP:\t%{SIGPGP:pgpsig}\nGPG:\t%{SIGGPG:pgpsig}\n' -p *.rpm + working-directory: signed-redhat-installer + + - name: Store Signed Linux RPM Packages + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: cf-cli-linux-rpm-packages-test + path: signed-redhat-installer/*.rpm + + - name: Build Debian Packages + env: + VERSION_BUILD: ${{ needs.setup.outputs.version-build }} + VERSION_MAJOR: ${{ needs.setup.outputs.version-major }} + run: | + set -ex + set -o pipefail + + root=$PWD + + mkdir -pv $root/packaged-deb + + echo "Build 32-bit Debian package" + ( + SIZE="$(BLOCKSIZE=1000 du $root/out/cf-cli_linux_i686 | cut -f 1)" + pushd cli-ci/ci/installers/deb + mkdir -p cf/usr/bin cf/usr/share/doc/cf${VERSION_MAJOR}-cli/ cf/DEBIAN cf/usr/share/bash-completion/completions + cp copyright_preamble cf/DEBIAN/copyright + sed 's/^$/ ./' $root/LICENSE >> cf/DEBIAN/copyright + cat copyright_comment_header >> cf/DEBIAN/copyright + sed 's/^$/ ./' ../../license/3RD-PARTY-LICENSES >> cf/DEBIAN/copyright + cp cf/DEBIAN/copyright cf/usr/share/doc/cf${VERSION_MAJOR}-cli/copyright + cp ../../license/NOTICE cf/usr/share/doc/cf${VERSION_MAJOR}-cli + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES cf/usr/share/doc/cf${VERSION_MAJOR}-cli/LICENSE + cp control_v${VERSION_MAJOR}.template cf/DEBIAN/control + echo "Installed-Size: ${SIZE}" >> cf/DEBIAN/control + echo "Version: ${VERSION_BUILD}" >> cf/DEBIAN/control + echo "Architecture: i386" >> cf/DEBIAN/control + cp ../completion/cf${VERSION_MAJOR} cf/usr/share/bash-completion/completions/cf${VERSION_MAJOR} + cp $root/out/cf-cli_linux_i686 cf/usr/bin/cf${VERSION_MAJOR} + ln -frs cf/usr/bin/cf${VERSION_MAJOR} cf/usr/bin/cf + fakeroot dpkg --build cf cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_i686.deb + mv cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_i686.deb $root/packaged-deb + rm -rf cf + popd + ) + + echo "Build x86 64-bit Debian package" + ( + SIZE="$(BLOCKSIZE=1000 du $root/out/cf-cli_linux_x86-64 | cut -f 1)" + pushd cli-ci/ci/installers/deb + mkdir -p cf/usr/bin cf/usr/share/doc/cf${VERSION_MAJOR}-cli/ cf/DEBIAN cf/usr/share/bash-completion/completions + cp copyright_preamble cf/DEBIAN/copyright + sed 's/^$/ ./' $root/LICENSE >> cf/DEBIAN/copyright + cat copyright_comment_header >> cf/DEBIAN/copyright + sed 's/^$/ ./' ../../license/3RD-PARTY-LICENSES >> cf/DEBIAN/copyright + cp cf/DEBIAN/copyright cf/usr/share/doc/cf${VERSION_MAJOR}-cli/copyright + cp ../../license/NOTICE cf/usr/share/doc/cf${VERSION_MAJOR}-cli + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES cf/usr/share/doc/cf${VERSION_MAJOR}-cli/LICENSE + cp control_v${VERSION_MAJOR}.template cf/DEBIAN/control + echo "Installed-Size: ${SIZE}" >> cf/DEBIAN/control + echo "Version: ${VERSION_BUILD}" >> cf/DEBIAN/control + echo "Architecture: amd64" >> cf/DEBIAN/control + cp ../completion/cf${VERSION_MAJOR} cf/usr/share/bash-completion/completions/cf${VERSION_MAJOR} + cp $root/out/cf-cli_linux_x86-64 cf/usr/bin/cf${VERSION_MAJOR} + ln -frs cf/usr/bin/cf${VERSION_MAJOR} cf/usr/bin/cf + fakeroot dpkg --build cf cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_x86-64.deb + mv cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_x86-64.deb $root/packaged-deb + popd + ) + + echo "Build ARM 64-bit Debian package" + ( + SIZE="$(BLOCKSIZE=1000 du $root/out/cf-cli_linux_arm64 | cut -f 1)" + pushd cli-ci/ci/installers/deb + mkdir -p cf/usr/bin cf/usr/share/doc/cf${VERSION_MAJOR}-cli/ cf/DEBIAN cf/usr/share/bash-completion/completions + cp copyright_preamble cf/DEBIAN/copyright + sed 's/^$/ ./' $root/LICENSE >> cf/DEBIAN/copyright + cat copyright_comment_header >> cf/DEBIAN/copyright + sed 's/^$/ ./' ../../license/3RD-PARTY-LICENSES >> cf/DEBIAN/copyright + cp cf/DEBIAN/copyright cf/usr/share/doc/cf${VERSION_MAJOR}-cli/copyright + cp ../../license/NOTICE cf/usr/share/doc/cf${VERSION_MAJOR}-cli + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES cf/usr/share/doc/cf${VERSION_MAJOR}-cli/LICENSE + cp control_v${VERSION_MAJOR}.template cf/DEBIAN/control + echo "Installed-Size: ${SIZE}" >> cf/DEBIAN/control + echo "Version: ${VERSION_BUILD}" >> cf/DEBIAN/control + echo "Architecture: arm64" >> cf/DEBIAN/control + cp ../completion/cf${VERSION_MAJOR} cf/usr/share/bash-completion/completions/cf${VERSION_MAJOR} + cp $root/out/cf-cli_linux_arm64 cf/usr/bin/cf${VERSION_MAJOR} + ln -frs cf/usr/bin/cf${VERSION_MAJOR} cf/usr/bin/cf + fakeroot dpkg --build cf cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_arm64.deb + mv cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_arm64.deb $root/packaged-deb + popd + ) + + - name: Print DEB Packages Info + run: | + ls -R + for f in *.deb; do + echo $f + dpkg --info $f + done + working-directory: packaged-deb + + - name: Store Debian Packages + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: cf-cli-linux-deb-packages-test + path: packaged-deb/*.deb + + test-rpm-package: + name: Test RPM Artifacts + needs: + - setup + - build-linux + runs-on: ubuntu-latest + container: + image: fedora + steps: + + - name: Download Signed Linux Packages + uses: actions/download-artifact@v4 + with: + name: cf-cli-linux-rpm-packages-test + + - name: Display structure of downloaded files + run: ls -R + + - name: Test RPMs + run: | + rpm -q --qf 'FN:\t%{FILENAMES}\nNAME:\t%{NAME}\nPGP:\t%{SIGPGP:pgpsig}\nGPG:\t%{SIGGPG:pgpsig}\n' -p *.rpm + + test-deb-package: + name: Test Debian Artifacts + needs: + - setup + - build-linux + runs-on: ubuntu-20.04 + container: + image: ubuntu:20.04 + steps: + + - name: Download Signed Linux Packages + uses: actions/download-artifact@v4 + with: + name: cf-cli-linux-deb-packages-test + + - name: Display structure of downloaded files run: | - docker run -i --rm -v ${{ github.workspace }}:/workspace -w /workspace ubuntu:20.04 << EOM - cat /etc/os-release - EOM \ No newline at end of file + ls -R + ls *.deb | xargs -n1 dpkg --info From d47272ddc594b6678e03e64661592949a94ab887 Mon Sep 17 00:00:00 2001 From: Al Berez Date: Thu, 6 Mar 2025 12:09:23 -0800 Subject: [PATCH 3/3] Wrap deb packaging into docker container Context: - https://github.com/cloudfoundry/cli/commit/b5a352a685bdafa86f1552870ace148ee5dd7137 - https://github.com/cloudfoundry/cli/issues/2376 --- .github/workflows/release-bump-gpg.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release-bump-gpg.yml b/.github/workflows/release-bump-gpg.yml index e5ce7ecfb69..1a7955ae51e 100644 --- a/.github/workflows/release-bump-gpg.yml +++ b/.github/workflows/release-bump-gpg.yml @@ -238,6 +238,7 @@ jobs: VERSION_BUILD: ${{ needs.setup.outputs.version-build }} VERSION_MAJOR: ${{ needs.setup.outputs.version-major }} run: | + docker run -i --rm -v ${{ github.workspace }}:/workspace -w /workspace ubuntu:20.04 << EOM set -ex set -o pipefail @@ -317,14 +318,17 @@ jobs: mv cf${VERSION_MAJOR}-cli-installer_${VERSION_BUILD}_arm64.deb $root/packaged-deb popd ) + EOM - name: Print DEB Packages Info run: | + docker run -i --rm -v ${{ github.workspace }}:/workspace -w /workspace ubuntu:20.04 << EOM ls -R for f in *.deb; do echo $f dpkg --info $f done + EOM working-directory: packaged-deb - name: Store Debian Packages @@ -373,5 +377,7 @@ jobs: - name: Display structure of downloaded files run: | + docker run -i --rm -v ${{ github.workspace }}:/workspace -w /workspace ubuntu:20.04 << EOM ls -R ls *.deb | xargs -n1 dpkg --info + EOM