diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1a21cb34 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,174 @@ +name: Release SDK on GitHub + +on: + workflow_dispatch: + inputs: + version: + description: "Version number" + required: true + type: string + build_number: + description: "Build number " + required: true + type: string + +jobs: + prepare-release: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/heads/sdk-core/') # Only run on branches that start with sdk-core/ + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Parse and Validate Inputs + run: | + # Get inputs passed to the workflow + VERSION="${{ github.event.inputs.version }}" + BUILD_NUMBER="${{ github.event.inputs.build_number }}" + + # Save the parsed values for future steps + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV + shell: bash + + - name: Run the Prep Release Script + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + make prep-release VERSION="$VERSION" BUILD_NUMBER="$BUILD_NUMBER" + shell: bash + + build-wheels: + name: Build wheels for Python SDK on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + if: startsWith(github.ref, 'refs/heads/sdk-core/') + needs: [prepare-release] + strategy: + fail-fast: false + matrix: + # macOS 13 is an Intel runner and macOS 14 is an Apple Silicon runner + os: [ubuntu-22.04, ubuntu-22.04-arm, windows-latest, macos-13, macos-14] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Upgrade build dependencies + run: python -m pip install --upgrade pip setuptools wheel + + # Need to grab the SDK version for the wheel name + - name: Extract SDK Version + run: echo "SDK_VERSION=$(cat version.txt)" >> "$GITHUB_ENV" + shell: bash + + - name: Install cibuildwheel + run: | + python -m pip install cibuildwheel + + - name: Build wheels + env: + CIBW_SKIP: pp* *-musllinux_* + CIBW_MANYLINUX_X86_64_IMAGE: "quay.io/pypa/manylinux_2_34_x86_64" + CIBW_MANYLINUX_AARCH64_IMAGE: "quay.io/pypa/manylinux_2_34_aarch64" + CIBW_ARCHS: "native" + CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel" + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}" + CIBW_TEST_REQUIRES: "pydantic pytest pytest-asyncio" + MACOSX_DEPLOYMENT_TARGET: "12.0" + CIBW_TEST_COMMAND: "python -m pytest {project}/src/onepassword/test_client.py" + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.TEST_SERVICE_ACCOUNT_TOKEN }} + CIBW_ENVIRONMENT_PASS_LINUX: OP_SERVICE_ACCOUNT_TOKEN # We have to specify this to pass the token to the test command + run: | + python -m cibuildwheel --output-dir dist + + - uses: actions/upload-artifact@v4 + with: + name: onepassword-sdk-${{ env.SDK_VERSION }}-${{ matrix.os }} + path: ./dist/*.whl +# test + build-sdist: + name: Build source distribution for Python SDK + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/heads/sdk-core/') + needs: [prepare-release] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + # Need to grab the SDK version for the wheel name + - name: Extract SDK Version + run: echo "SDK_VERSION=$(cat version.txt)" >> "$GITHUB_ENV" + shell: bash + + - name: Install dependencies + run: pip3 install build pydantic pytest pytest-asyncio + + - name: Build source distribution + run: python3 -m build --sdist + + - name: Test Source Distribution + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.TEST_SERVICE_ACCOUNT_TOKEN }} + run: | + python3 -m pip install dist/*.tar.gz + python3 -m pytest src/onepassword/test_client.py + + - uses: actions/upload-artifact@v4 + with: + name: onepassword-sdk-${{ env.SDK_VERSION }} + path: ./dist/*.tar.gz + + Release-SDK: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/heads/sdk-core/') # Only run on branches that start with sdk-core/ + needs: [build-wheels, build-sdist] + steps: + - name: Checkout the code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.ref }} + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true + + - name: Run the Release Script + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: make release + shell: bash + + publish-to-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/heads/sdk-core/') + environment: + name: testpypi + url: https://test.pypi.org/project/onepassword-sdk/ + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + needs: [release-sdk] + steps: + - uses: actions/download-artifact@v4 + with: + pattern: onepassword-sdk-* + path: ./dist + merge-multiple: true + - name: Publish package distributions to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1.12 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/Makefile b/Makefile index 17592d1d..6ffb86e5 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ release: src/release/scripts/release.sh prep-release: - src/release/scripts/prep-release.sh + src/release/scripts/prep-release.sh $(VERSION) $(BUILD_NUMBER) "$(RELEASE_NOTES)" build-wheels: src/release/scripts/build-wheels.sh $(PYTHON_VERSIONS) @@ -12,7 +12,7 @@ build-wheels: release/install-dependencies: # Install latest version of pyenv if not already installed brew install pyenv - + # Install all the python versions we support in one line pyenv install --skip-existing $(PYTHON_VERSIONS) @@ -21,4 +21,3 @@ release/install-dependencies: pyenv local $$version; \ pyenv exec pip3 install wheel setuptools build --break-system-packages; \ done - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..db4245ac --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools>=66", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "onepassword-sdk" +dynamic = ["version"] +description = "The 1Password Python SDK offers programmatic read access to your secrets in 1Password in an interface native to Python." +authors = [{ name = "1Password" }] +license = { file = "LICENSE" } +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "pydantic>=2.5", +] + +[project.urls] +Homepage = "https://github.com/1Password/onepassword-sdk-python" + +[tool.setuptools.dynamic] +version = {file = "./version.txt"} \ No newline at end of file diff --git a/setup.py b/setup.py index 9e4a7b7a..da45642b 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,5 @@ -from pathlib import Path -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Distribution from sysconfig import get_platform -from version import SDK_VERSION import platform import os @@ -19,6 +17,9 @@ def finalize_options(self): except ImportError: bdist_wheel = None +class BinaryDistribution(Distribution): + def has_ext_modules(self): + return True def get_shared_library_data_to_include(): # Return the correct uniffi C shared library extension for the given platform @@ -46,35 +47,11 @@ def get_shared_library_data_to_include(): setup( - name="onepassword-sdk", - version=SDK_VERSION, - author="1Password", - long_description=(Path(__file__).parent / "README.md").read_text(), - long_description_content_type="text/markdown", - description="The 1Password Python SDK offers programmatic read access to your secrets in 1Password in an interface native to Python.", - url="https://github.com/1Password/onepassword-sdk-python", packages=find_packages( where="src", ), - license="MIT", - license_files="LICENSE", + distclass=BinaryDistribution, package_dir={"": "src"}, - python_requires=">=3.9", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "License :: OSI Approved :: MIT License", - ], cmdclass={"bdist_wheel": bdist_wheel}, package_data={"": get_shared_library_data_to_include()}, - install_requires=[ - "pydantic>=2.5", # Minimum Pydantic version to run the Python SDK - ], ) diff --git a/src/onepassword/build_number.py b/src/onepassword/build_number.py index 687a1c06..7e6ddeda 100644 --- a/src/onepassword/build_number.py +++ b/src/onepassword/build_number.py @@ -1 +1 @@ -SDK_BUILD_NUMBER = "0020001" +SDK_BUILD_NUMBER = "0080200" \ No newline at end of file diff --git a/src/onepassword/core.py b/src/onepassword/core.py index 5f6af199..807be338 100644 --- a/src/onepassword/core.py +++ b/src/onepassword/core.py @@ -3,7 +3,7 @@ from onepassword.errors import raise_typed_exception -# In empirical tests, we determined that maximum message size that can cross the FFI boundary +# In empirical tests, we determined that maximum message size that can cross the FFI boundary # is ~128MB. Past this limit, FFI will throw an error and the program will crash. # We set the limit to 50MB to be safe and consistent with the other SDKs (where this limit is 64MB), to be reconsidered upon further testing MESSAGE_LIMIT = 50 * 1024 * 1024 @@ -19,7 +19,6 @@ f"Your machine's architecture is not currently supported: {machine_arch}" ) - # InitClient creates a client instance in the current core module and returns its unique ID. async def _init_client(client_config): try: diff --git a/src/onepassword/lib/aarch64/libop_uniffi_core.dylib b/src/onepassword/lib/aarch64/libop_uniffi_core.dylib index efc3ecaf..44168082 100755 Binary files a/src/onepassword/lib/aarch64/libop_uniffi_core.dylib and b/src/onepassword/lib/aarch64/libop_uniffi_core.dylib differ diff --git a/src/onepassword/lib/aarch64/libop_uniffi_core.so b/src/onepassword/lib/aarch64/libop_uniffi_core.so index 40849eab..a386ee58 100755 Binary files a/src/onepassword/lib/aarch64/libop_uniffi_core.so and b/src/onepassword/lib/aarch64/libop_uniffi_core.so differ diff --git a/src/onepassword/lib/x86_64/libop_uniffi_core.dylib b/src/onepassword/lib/x86_64/libop_uniffi_core.dylib index 752e0361..3dd54d35 100755 Binary files a/src/onepassword/lib/x86_64/libop_uniffi_core.dylib and b/src/onepassword/lib/x86_64/libop_uniffi_core.dylib differ diff --git a/src/onepassword/lib/x86_64/libop_uniffi_core.so b/src/onepassword/lib/x86_64/libop_uniffi_core.so index 011343cb..015db2f9 100755 Binary files a/src/onepassword/lib/x86_64/libop_uniffi_core.so and b/src/onepassword/lib/x86_64/libop_uniffi_core.so differ diff --git a/src/onepassword/lib/x86_64/op_uniffi_core.dll b/src/onepassword/lib/x86_64/op_uniffi_core.dll index 077d5e56..21b3ef66 100644 Binary files a/src/onepassword/lib/x86_64/op_uniffi_core.dll and b/src/onepassword/lib/x86_64/op_uniffi_core.dll differ diff --git a/src/onepassword/test_client.py b/src/onepassword/test_client.py index 8549166c..f3adf6ee 100644 --- a/src/onepassword/test_client.py +++ b/src/onepassword/test_client.py @@ -8,7 +8,6 @@ ## test resolve function - # valid @pytest.mark.asyncio async def test_valid_resolve(): @@ -18,7 +17,7 @@ async def test_valid_resolve(): integration_version=onepassword_defaults.DEFAULT_INTEGRATION_VERSION, ) result = await client.secrets.resolve( - secret_reference="op://gowwbvgow7kxocrfmfvtwni6vi/6ydrn7ne6mwnqc2prsbqx4i4aq/password" + secret_reference="op://bhld6zk6hkuntyqlsjy3bdawey/jrtghaxr4hybspesgij35g5myy/password" ) assert result == "test_password_42" diff --git a/src/release/RELEASE-NOTES b/src/release/RELEASE-NOTES index f9e50bbf..ee2ad8d8 100644 --- a/src/release/RELEASE-NOTES +++ b/src/release/RELEASE-NOTES @@ -1,13 +1,20 @@ -# 1Password Python SDK v0.2.0 +# Release notes SDKs 0.5.0 ## NEW -- **File Support:** You can now create Document items, attach files to items, delete files from items, and read file contents using the SDK. +- ** and item metadata:** Items and item overviews ****now expose attributes with their creation and last edit times**.** +- **Resolving secrets in bulk**: With the function, the SDK is now able to resolve multiple secrets at once, improving the performance of the operation. ## IMPROVED -- **Read files using secret references**: You can now resolve secret references that point to files attached to 1Password items. -- **Read SSH keys in Open SSH format**: You can now use a secret reference to fetch a private key in OpenSSH format. For example: `op://vault//private key?ssh-format=openssh` -- **Support for more item field types**: You can now create, retrieve, and edit items containing SSH keys, Month-Year and Menu-type fields using the SDK. -- **Read more field types using secret references**: You can now resolve secret references that point to information stored in Date, Month/Year, Address, and Reference field types. -- **Improved error messages**: The error messages returned by the SDK were improved to be more clear and actionable. +- **Support for new field types:** Items with and fields can now be created, retrieved, and edited using the 1Password SDK. +- **Item sharing for attachments and documents**: Items with files attached can now be shared. +- **Adding custom fields in sections automatically**: If a custom field has no specified section, the SDKs now automatically add the custom field to an empty section within the item, creating it if necessary. +- ** in item overviews**: The return type of now also contains the item tags. +- **Broader item editing capabilities**: You can now use the items.put function with more item types, including those with fields that are not directly editable through the SDK, like legacy fields, or passkeys. + +## FIXED + +- **Improvements to resolving secret references:** + - Archived items are no longer used for secret references. + - When multiple sections match a section query when resolving secret references, the SDKs now look through the fields in all matching sections instead of erroring. diff --git a/src/release/scripts/build-wheels.sh b/src/release/scripts/build-wheels.sh index 484875d3..d26fdec4 100755 --- a/src/release/scripts/build-wheels.sh +++ b/src/release/scripts/build-wheels.sh @@ -15,7 +15,7 @@ macOS_version_x86_64=10.9 macOS_version_arm64=11.0 # Extracts the current verison number for cleanup function -current_version=$(awk -F "['\"]" '/SDK_VERSION =/{print $2}' "$output_version_file") +current_version=$(cat version.txt) # Function to execute upon exit cleanup() { diff --git a/src/release/scripts/prep-release.sh b/src/release/scripts/prep-release.sh index 88e709d2..1abb7fbf 100755 --- a/src/release/scripts/prep-release.sh +++ b/src/release/scripts/prep-release.sh @@ -2,31 +2,36 @@ # Helper script to prepare a release for the Python SDK. -output_version_file="version.py" +output_version_file="version.txt" output_build_file="src/onepassword/build_number.py" -version_template_file="src/release/templates/version.tpl.py" build_number_template_file="src/release/templates/build_number.tpl.py" +version=$1 +build=$2 -# Extracts the current build/version number for comparison and backup -current_version=$(awk -F "['\"]" '/SDK_VERSION =/{print $2}' "$output_version_file") +# Extracts the current build/version number for comparison and backup +current_version=$(cat "$output_version_file" 2>/dev/null || echo "") current_build=$(awk -F "['\"]" '/SDK_BUILD_NUMBER =/{print $2}' "$output_build_file") # Function to execute upon exit cleanup() { echo "Performing cleanup tasks..." # Revert changes to file if any - sed -e "s/{{ version }}/$current_version/" "$version_template_file" > "$output_version_file" - sed -e "s/{{ build }}/$current_build/" "$build_number_template_file" > "$output_build_file" - exit 1 + echo -n "$current_version" > "$output_version_file" + echo -n "SDK_BUILD_NUMBER = \"$current_build\"" > "$output_build_file" + exit 1 } # Set the trap to call the cleanup function on exit trap cleanup SIGINT enforce_latest_code() { - if [[ -n "$(git status --porcelain=v1)" ]]; then - echo "ERROR: working directory is not clean." + # Define the file to skip (relative path) + SKIP_FILE="src/release/RELEASE-NOTES" + + # Check if there are any uncommitted changes, excluding the specific file + if [[ -n "$(git status --porcelain=v1 | grep -v "$SKIP_FILE")" ]]; then + echo "ERROR: working directory is not clean (excluding $SKIP_FILE)." echo "Please stash your changes and try again." exit 1 fi @@ -35,18 +40,15 @@ enforce_latest_code() { # Function to validate the version number format x.y.z(-beta.w) update_and_validate_version() { while true; do - # Prompt the user to input the version number - read -p "Enter the version number (format: x.y.z(-beta.w)): " version - # Validate the version number format - if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$ ]]; then + if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$ ]]; then if [[ "${current_version}" != "${version}" ]]; then # TODO: Check the less than case as well. echo "New version number is: ${version}" return 0 else echo "Version hasn't changed." - fi + fi else echo "Invalid version number format: ${version}" echo "Please enter a version number in the 'x.y.z(-beta.w)' format." @@ -55,12 +57,9 @@ update_and_validate_version() { } # Function to validate the build number format. -# SEMVER Format: Mmmppbb - 7 Digits +# SEMVER Format: Mmmppbb - 7 Digits update_and_validate_build() { while true; do - # Prompt the user to input the build number - read -p "Enter the build number (format: Mmmppbb): " build - # Validate the build number format if [[ "${build}" =~ ^[0-9]{7}$ ]]; then if (( 10#$current_build < 10#$build )); then @@ -83,17 +82,12 @@ enforce_latest_code # Update and validate the version number update_and_validate_version -# Update and validate the build number -update_and_validate_build +# Update and validate the build number / +update_and_validate_build -# Update version & build number in version.py and build_number.py respectively -sed -e "s/{{ version }}/$version/" "$version_template_file" > "$output_version_file" -sed -e "s/{{ build }}/$build/" "$build_number_template_file" > "$output_build_file" - - -printf "Press ENTER to edit the RELEASE-NOTES in your default editor...\n" -read -r _ignore -${EDITOR:-nano} "src/release/RELEASE-NOTES" +# Update version & build number in the appropriate files +echo -n "$version" > "$output_version_file" +echo -n "SDK_BUILD_NUMBER = \"$build\"" > "$output_build_file" # Get Current Branch Name branch="$(git rev-parse --abbrev-ref HEAD)" @@ -116,4 +110,3 @@ echo "Release has been prepared.. Make sure to double check version/build numbers in their appropriate files and changelog is correctly filled out. Once confirmed, run 'make release' to release the SDK!" - diff --git a/src/release/scripts/release.sh b/src/release/scripts/release.sh index 050c1489..0fa9b1ca 100755 --- a/src/release/scripts/release.sh +++ b/src/release/scripts/release.sh @@ -5,7 +5,7 @@ set -e # Read the contents of the files into variables -version=$(awk -F "['\"]" '/SDK_VERSION =/{print $2}' "version.py") +version=$(cat "version.txt") build=$(awk -F "['\"]" '/SDK_BUILD_NUMBER =/{print $2}' "src/onepassword/build_number.py") release_notes=$(< src/release/RELEASE-NOTES) @@ -26,11 +26,4 @@ git tag -a -s "v${version}" -m "${version}" # Push the tag to the branch git push origin tag "v${version}" -gh release create "v${version}" --title "Release ${version}" --notes "${release_notes}" --repo github.com/1Password/onepassword-sdk-python - -# Release on PyPi -python3 -m twine upload dist/* - -# Delete the dist folder after published -rm -r dist src/*.egg-info - +gh release create "v${version}" --title "Release ${version}" --notes "${release_notes}" --repo github.com/MOmarMiraj/onepassword-sdk-python diff --git a/src/release/templates/version.tpl.py b/src/release/templates/version.tpl.py deleted file mode 100644 index 9c787be2..00000000 --- a/src/release/templates/version.tpl.py +++ /dev/null @@ -1 +0,0 @@ -SDK_VERSION = "{{ version }}" diff --git a/version.py b/version.py deleted file mode 100644 index f5ebd34f..00000000 --- a/version.py +++ /dev/null @@ -1 +0,0 @@ -SDK_VERSION = "0.2.0" diff --git a/version.txt b/version.txt new file mode 100644 index 00000000..53a48a1e --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.8.2 \ No newline at end of file