Search code examples
pythonamazon-web-servicespippython-poetryaws-codeartifact

How can I publish Python packages to CodeArtifact using Poetry?


Trying to publish a Poetry package to AWS CodeArtifact. It supports pip which should indicate that it supports poetry as well since poetry can upload to PyPi servers.

I've configured the domain like so:

export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain XXXX --domain-owner XXXXXXXXXXXX --query authorizationToken --output text`
poetry config repositories.the_aws_repo https://aws:$CODEARTIFACT_AUTH_TOKEN@XXXX-XXXXXXXXXXXX.d.codeartifact.eu-central-1.amazonaws.com/pypi/XXXX/simple/
poetry config pypi-token.the_aws_repo $CODEARTIFACT_AUTH_TOKEN

But I'm getting 404 when trying to publish the package:

❯ poetry publish --repository the_aws_repo -vvv

No suitable keyring backend found
No suitable keyring backends were found
Using a plaintext file to store and retrieve credentials
Publishing xxx (0.1.5) to the_aws_repo
 - Uploading xxx-0.1.5-py3-none-any.whl 100%

  Stack trace:

  7  ~/.poetry/lib/poetry/_vendor/py3.8/clikit/console_application.py:131 in run
      129│             parsed_args = resolved_command.args
      130│
    → 131│             status_code = command.handle(parsed_args, io)
      132│         except KeyboardInterrupt:
      133│             status_code = 1

  6  ~/.poetry/lib/poetry/_vendor/py3.8/clikit/api/command/command.py:120 in handle
      118│     def handle(self, args, io):  # type: (Args, IO) -> int
      119│         try:
    → 120│             status_code = self._do_handle(args, io)
      121│         except KeyboardInterrupt:
      122│             if io.is_debug():

  5  ~/.poetry/lib/poetry/_vendor/py3.8/clikit/api/command/command.py:171 in _do_handle
      169│         handler_method = self._config.handler_method
      170│
    → 171│         return getattr(handler, handler_method)(args, io, self)
      172│
      173│     def __repr__(self):  # type: () -> str

  4  ~/.poetry/lib/poetry/_vendor/py3.8/cleo/commands/command.py:92 in wrap_handle
       90│         self._command = command
       91│
    →  92│         return self.handle()
       93│
       94│     def handle(self):  # type: () -> Optional[int]

  3  ~/.poetry/lib/poetry/console/commands/publish.py:77 in handle
      75│         )
      76│
    → 77│         publisher.publish(
      78│             self.option("repository"),
      79│             self.option("username"),

  2  ~/.poetry/lib/poetry/publishing/publisher.py:93 in publish
      91│         )
      92│
    → 93│         self._uploader.upload(
      94│             url,
      95│             cert=cert or get_cert(self._poetry.config, repository_name),

  1  ~/.poetry/lib/poetry/publishing/uploader.py:119 in upload
      117│
      118│         try:
    → 119│             self._upload(session, url, dry_run)
      120│         finally:
      121│             session.close()

  UploadError

  HTTP Error 404: Not Found

  at ~/.poetry/lib/poetry/publishing/uploader.py:216 in _upload
      212│                     self._register(session, url)
      213│                 except HTTPError as e:
      214│                     raise UploadError(e)
      215│
    → 216│             raise UploadError(e)
      217│
      218│     def _do_upload(
      219│         self, session, url, dry_run=False
      220│     ):  # type: (requests.Session, str, Optional[bool]) -> None

My AWS IAM user has permission to do this since I gave it the relevant permissions in the repo.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::XXXXXXXXXXXX:user/ShayN"
            },
            "Action": [
                "codeartifact:AssociateExternalConnection",
                "codeartifact:CopyPackageVersions",
                "codeartifact:DeletePackageVersions",
                "codeartifact:DeleteRepository",
                "codeartifact:DeleteRepositoryPermissionsPolicy",
                "codeartifact:DescribePackageVersion",
                "codeartifact:DescribeRepository",
                "codeartifact:DisassociateExternalConnection",
                "codeartifact:DisposePackageVersions",
                "codeartifact:GetPackageVersionReadme",
                "codeartifact:GetRepositoryEndpoint",
                "codeartifact:ListPackageVersionAssets",
                "codeartifact:ListPackageVersionDependencies",
                "codeartifact:ListPackageVersions",
                "codeartifact:ListPackages",
                "codeartifact:PublishPackageVersion",
                "codeartifact:PutPackageMetadata",
                "codeartifact:PutRepositoryPermissionsPolicy",
                "codeartifact:ReadFromRepository",
                "codeartifact:UpdatePackageVersionsStatus",
                "codeartifact:UpdateRepository"
            ],
            "Resource": "*"
        }
    ]
}

What am I missing?


Solution

  • The problem is the /simple/ at the end of the repo url. This part should only be added when pulling from that repo, not when publishing to it. If you look closely to the documentation of AWS CodeArtifact on how to publish with twine, you'll see that it's also not there.

    This works:

    # This will give the repo url without the /simple/ part
    # Example: https://<my-domain>-<domain-owner-id>.d.codeartifact.<region>.amazonaws.com/pypi/<my-repo>/
    # Note the lack of the "aws:auth-token@" part
    export CODEARTIFACT_REPOSITORY_URL=`aws codeartifact get-repository-endpoint --domain my-domain --domain-owner domain-owner-id --repository my-repo --format pypi --query repositoryEndpoint --output text`
    
    # This will give the token to access the repo
    export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain my-domain --domain-owner domain-owner-id --query authorizationToken --output text`
    
    # This specifies the user who accesses the repo
    export CODEARTIFACT_USER=aws
    
    # Now use all of these when configuring the repo in poetry
    poetry config repositories.<my-repo-name-for-poetry> $CODEARTIFACT_REPOSITORY_URL
    poetry config http-basic.<my-repo-name-for-poetry> $CODEARTIFACT_USER $CODEARTIFACT_AUTH_TOKEN
    

    Note that the authentication token will expire when your AWS login session ends. Hence, you'll have to set the http-basic.<my-repo-name-for-poetry> with the new token every time it expires.

    FYI, I had the same problem and it took me hours to figure this out. But in the end, more carefully reading the documentation should have helped me.