Exemple #1
0
def test_ociregistry_upload_blob_bad_response_middle(tmp_path, responses,
                                                     monkeypatch):
    """Bad response from the server when pumping bytes."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    base_url = "https://fakereg.com/v2/test-image/"

    # fake the first initial response
    pump_url_1 = base_url + "fakeurl-1"
    responses.add(
        responses.POST,
        base_url + "blobs/uploads/",
        status=202,
        headers={
            "Location": pump_url_1,
            "Range": "0-0"
        },
    )

    # and the intermediate ones, chained, with a crash
    pump_url_2 = base_url + "fakeurl-2"
    responses.add(responses.PATCH,
                  pump_url_1,
                  status=202,
                  headers={"Location": pump_url_2})
    responses.add(responses.PATCH, pump_url_2, status=504)

    # prepare a fake content that will be pushed in 3 parts
    monkeypatch.setattr(registry, "CHUNK_SIZE", 3)
    bytes_source = tmp_path / "testfile"
    bytes_source.write_text("abcdefgh")

    # call!
    msg = r"Wrong status code from server \(expected=202, got=504\).*"
    with pytest.raises(CommandError, match=msg):
        ocireg.upload_blob(bytes_source, 8, "test-digest")
def test_get_manifest_simple_v2(responses, caplog):
    """Straightforward download of a v2 manifest."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")

    ocireg = OCIRegistry("fakereg.com", "test-orga", "test-image")
    url = "https://fakereg.com/v2/test-orga/test-image/manifests/test-reference"
    response_headers = {"Docker-Content-Digest": "test-digest"}
    response_content = {
        "schemaVersion": 2,
        "foo": "bar",
        "unicodecontent": "moño"
    }
    responses.add(responses.GET,
                  url,
                  status=200,
                  headers=response_headers,
                  json=response_content)

    # try it
    sublist, digest, raw_manifest = ocireg.get_manifest("test-reference")
    assert sublist is None
    assert digest == "test-digest"
    assert raw_manifest == responses.calls[
        0].response.text  # must be exactly the same

    log_lines = [rec.message for rec in caplog.records]
    assert "Getting manifests list for test-reference" in log_lines
    assert "Got the manifest directly, schema 2" in log_lines

    assert responses.calls[0].request.headers["Accept"] == MANIFEST_LISTS
def test_auth_with_credentials(emitter, responses):
    """Authenticate passing credentials."""
    responses.add(
        responses.GET,
        "https://auth.fakereg.com?service=test-service&scope=test-scope",
        json={"token": "test-token"},
    )

    ocireg = OCIRegistry(
        "https://fakereg.com",
        "test-image",
        username="******",
        password="******",
    )
    auth_info = dict(realm="https://auth.fakereg.com",
                     service="test-service",
                     scope="test-scope")
    token = ocireg._authenticate(auth_info)
    assert token == "test-token"
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    expected_encoded = base64.b64encode(b"test-user:test-password")
    assert sent_auth_header == "Basic " + expected_encoded.decode("ascii")

    # generic auth indication is logged but NOT the credentials
    expected = "Authenticating! {}".format(auth_info)
    emitter.assert_trace(expected)
def test_get_manifest_bad_v2(responses, caplog):
    """Couldn't get a v2 manifest."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")
    ocireg = OCIRegistry("fakereg.com", "test-orga", "test-image")

    url = "https://fakereg.com/v2/test-orga/test-image/manifests/test-reference"
    response_headers = {"Docker-Content-Digest": "test-digest"}
    response_content = {"schemaVersion": 1}
    responses.add(responses.GET,
                  url,
                  status=200,
                  headers=response_headers,
                  json=response_content)

    # second response with a bad manifest
    url = "https://fakereg.com/v2/test-orga/test-image/manifests/test-reference"
    response_headers = {"Docker-Content-Digest": "test-digest-for-real"}
    response_content = {"sadly broken": ":("}
    responses.add(responses.GET,
                  url,
                  status=200,
                  headers=response_headers,
                  json=response_content)

    # try it
    with pytest.raises(CommandError) as cm:
        ocireg.get_manifest("test-reference")
    assert str(cm.value) == "Manifest v2 not found for 'test-reference'."
    expected = (
        "Got something else when asking for a v2 manifest: {'sadly broken': ':('}"
    )
    assert expected in [rec.message for rec in caplog.records]
def test_get_manifest_simple_multiple(responses):
    """Straightforward download of a multiple manifest."""
    ocireg = OCIRegistry("fakereg.com", "test-orga", "test-image")
    url = "https://fakereg.com/v2/test-orga/test-image/manifests/test-reference"
    response_headers = {"Docker-Content-Digest": "test-digest"}
    lot_of_manifests = [
        {
            "manifest1": "stuff"
        },
        {
            "manifest2": "morestuff",
            "foo": "bar"
        },
    ]
    response_content = {"manifests": lot_of_manifests}
    responses.add(responses.GET,
                  url,
                  status=200,
                  headers=response_headers,
                  json=response_content)

    # try it
    sublist, digest, raw_manifest = ocireg.get_manifest("test-reference")
    assert sublist == lot_of_manifests
    assert digest == "test-digest"
    assert raw_manifest == responses.calls[0].response.text  # exact
Exemple #6
0
def test_auth_with_credentials(caplog, responses):
    """Authenticate passing credentials."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")

    responses.add(
        responses.GET,
        "https://auth.fakereg.com?service=test-service&scope=test-scope",
        json={"token": "test-token"},
    )

    ocireg = OCIRegistry(
        "https://fakereg.com",
        "test-image",
        username="******",
        password="******",
    )
    auth_info = dict(realm="https://auth.fakereg.com",
                     service="test-service",
                     scope="test-scope")
    token = ocireg._authenticate(auth_info)
    assert token == "test-token"
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    expected_encoded = base64.b64encode(b"test-user:test-password")
    assert sent_auth_header == "Basic " + expected_encoded.decode("ascii")

    # generic auth indication is logged but NOT the credentials
    expected = "Authenticating! {}".format(auth_info)
    assert [expected] == [rec.message for rec in caplog.records]
Exemple #7
0
def test_ociregistry_is_item_uploaded_simple_no(responses):
    """Simple case for the item NOT already uploaded."""
    ocireg = OCIRegistry("http://fakereg.com/", "test-image")
    url = "http://fakereg.com/v2/test-image/stuff/some-reference"
    responses.add(responses.HEAD, url, status=404)

    # try it
    result = ocireg._is_item_already_uploaded(url)
    assert result is False
Exemple #8
0
def test_ociregistry_is_blob_uploaded():
    """Check the simple call with correct path to the generic verifier."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    with patch.object(ocireg, "_is_item_already_uploaded") as mock_verifier:
        mock_verifier.return_value = "whatever"
        result = ocireg.is_blob_already_uploaded("test-reference")
    assert result == "whatever"
    url = "https://fakereg.com/v2/test-image/blobs/test-reference"
    mock_verifier.assert_called_with(url)
def test_is_item_uploaded_simple_yes(responses):
    """Simple case for the item already uploaded."""
    ocireg = OCIRegistry("http://fakereg.com/", "test-orga", "test-image")
    url = "http://fakereg.com/v2/test-orga/test-image/stuff/some-reference"
    responses.add(responses.HEAD, url)

    # try it
    result = ocireg._is_item_already_uploaded(url)
    assert result is True
Exemple #10
0
def test_hit_different_method(responses):
    """Simple request using something else than GET."""
    # set the Registry with an initial token
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # fake a 200 response
    responses.add(responses.POST, "https://fakereg.com/api/stuff")

    # try it
    response = ocireg._hit("POST", "https://fakereg.com/api/stuff")
    assert response == responses.calls[0].response
Exemple #11
0
def test_ociregistry_upload_blob_bad_initial_response(responses):
    """Bad initial response when starting to upload."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    base_url = "https://fakereg.com/v2/test-image/"

    # fake the first initial response with problems
    responses.add(responses.POST, base_url + "blobs/uploads/", status=500)

    # call!
    msg = r"Wrong status code from server \(expected=202, got=500\).*"
    with pytest.raises(CommandError, match=msg):
        ocireg.upload_blob("test-filepath", 8, "test-digest")
def test_ociregistry_is_item_uploaded_strange_response(responses, emitter):
    """Unexpected response."""
    ocireg = OCIRegistry("http://fakereg.com/", "test-image")
    url = "http://fakereg.com/v2/test-image/stuff/some-reference"
    responses.add(responses.HEAD, url, status=400, headers={"foo": "bar"})

    # try it
    result = ocireg._is_item_already_uploaded(url)
    assert result is False
    expected = ("Bad response when checking for uploaded "
                "'http://fakereg.com/v2/test-image/stuff/some-reference': 400 "
                "(headers={'Content-Type': 'text/plain', 'foo': 'bar'})")
    emitter.assert_trace(expected)
Exemple #13
0
def test_hit_extra_parameters(responses):
    """The request can include extra parameters."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")

    # fake a 200 response
    responses.add(responses.PUT, "https://fakereg.com/api/stuff")

    # try it
    response = ocireg._hit("PUT",
                           "https://fakereg.com/api/stuff",
                           data=b"test-payload")
    assert response == responses.calls[0].response
    assert responses.calls[0].request.body == b"test-payload"
def test_hit_no_log(emitter, responses):
    """Simple request but avoiding log."""
    # set the Registry with an initial token
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # fake a 200 response
    responses.add(responses.PUT, "https://fakereg.com/api/stuff")

    # try it
    ocireg._hit("PUT", "https://fakereg.com/api/stuff", log=False)

    # nothing shown!
    emitter.assert_interactions(None)
Exemple #15
0
def test_ociregistry_is_item_uploaded_redirect(responses, redir_status):
    """The verification is redirected to somewhere else."""
    ocireg = OCIRegistry("http://fakereg.com/", "test-image")
    url1 = "http://fakereg.com/v2/test-image/stuff/some-reference"
    url2 = "http://fakereg.com/real-check/test-image/stuff/some-reference"
    responses.add(responses.HEAD,
                  url1,
                  status=redir_status,
                  headers={"Location": url2})
    responses.add(responses.HEAD, url2, status=200)

    # try it
    result = ocireg._is_item_already_uploaded(url1)
    assert result is True
Exemple #16
0
def test_ociregistry_is_item_uploaded_strange_response(responses, caplog):
    """Unexpected response."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")

    ocireg = OCIRegistry("http://fakereg.com/", "test-image")
    url = "http://fakereg.com/v2/test-image/stuff/some-reference"
    responses.add(responses.HEAD, url, status=400, headers={"foo": "bar"})

    # try it
    result = ocireg._is_item_already_uploaded(url)
    assert result is False
    expected = ("Bad response when checking for uploaded "
                "'http://fakereg.com/v2/test-image/stuff/some-reference': 400 "
                "(headers={'Content-Type': 'text/plain', 'foo': 'bar'})")
    assert expected in [rec.message for rec in caplog.records]
Exemple #17
0
def test_auth_simple(responses):
    """Simple authentication."""
    responses.add(
        responses.GET,
        "https://auth.fakereg.com?service=test-service&scope=test-scope",
        json={"token": "test-token"},
    )

    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    auth_info = dict(realm="https://auth.fakereg.com",
                     service="test-service",
                     scope="test-scope")
    token = ocireg._authenticate(auth_info)
    assert token == "test-token"
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    assert sent_auth_header is None
Exemple #18
0
def test_hit_simple_re_auth_problems(responses):
    """Bad response from the re-authentication process."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")

    # set only one response, a 401 which is broken and all will end there
    headers = {"Www-Authenticate": "broken header"}
    responses.add(responses.GET,
                  "https://fakereg.com/api/stuff",
                  headers=headers,
                  status=401)

    # try it, isolating the re-authentication (tested separatedly above)
    expected = ("Bad 401 response: Bearer not found; "
                "headers: {.*'Www-Authenticate': 'broken header'.*}")
    with pytest.raises(CommandError, match=expected):
        ocireg._hit("GET", "https://fakereg.com/api/stuff")
Exemple #19
0
def test_hit_no_log(caplog, responses):
    """Simple request but avoiding log."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")

    # set the Registry with an initial token
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # fake a 200 response
    responses.add(responses.PUT, "https://fakereg.com/api/stuff")

    # try it
    ocireg._hit("PUT", "https://fakereg.com/api/stuff", log=False)

    # no logs!
    assert not caplog.records
def test_ociregistry_upload_manifest_v2(responses, emitter):
    """Upload a V2 manifest."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")

    url = "https://fakereg.com/v2/test-image/manifests/test-reference"
    responses.add(responses.PUT, url, status=201)

    # try it
    raw_manifest_data = "test-manifest"
    ocireg.upload_manifest(raw_manifest_data, "test-reference")

    # check logs
    emitter.assert_progress("Uploading manifest with reference test-reference")
    emitter.assert_progress("Manifest uploaded OK")

    # check header and data sent
    assert responses.calls[0].request.headers[
        "Content-Type"] == MANIFEST_V2_MIMETYPE
    assert responses.calls[0].request.body == raw_manifest_data.encode("ascii")
Exemple #21
0
def test_auth_with_just_username(caplog, responses):
    """Authenticate passing credentials."""
    responses.add(
        responses.GET,
        "https://auth.fakereg.com?service=test-service&scope=test-scope",
        json={"token": "test-token"},
    )

    ocireg = OCIRegistry("https://fakereg.com",
                         "test-image",
                         username="******")
    auth_info = dict(realm="https://auth.fakereg.com",
                     service="test-service",
                     scope="test-scope")
    token = ocireg._authenticate(auth_info)
    assert token == "test-token"
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    expected_encoded = base64.b64encode(b"test-user:"******"Basic " + expected_encoded.decode("ascii")
Exemple #22
0
def test_hit_including_headers(responses):
    """A request including more headers."""
    # set the Registry with an initial token
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # fake a 200 response
    responses.add(responses.POST, "https://fakereg.com/api/stuff")

    # try it
    response = ocireg._hit("POST",
                           "https://fakereg.com/api/stuff",
                           headers={"FOO": "bar"})
    assert response == responses.calls[0].response

    # check that it sent the requested header AND the automatic auth one
    sent_headers = responses.calls[0].request.headers
    assert sent_headers.get("FOO") == "bar"
    assert sent_headers.get("Authorization") == "Bearer some auth token"
def test_hit_simple_initial_auth_ok(emitter, responses):
    """Simple GET with auth working at once."""
    # set the Registry with an initial token
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # fake a 200 response
    responses.add(responses.GET, "https://fakereg.com/api/stuff")

    # try it
    response = ocireg._hit("GET", "https://fakereg.com/api/stuff")
    assert response == responses.calls[0].response

    # verify it authed ok
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    assert sent_auth_header == "Bearer some auth token"

    # logged what it did
    expected = "Hitting the registry: GET https://fakereg.com/api/stuff"
    emitter.assert_trace(expected)
Exemple #24
0
def test_ociregistry_upload_blob_bad_upload_range(responses):
    """Received a broken range info."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    base_url = "https://fakereg.com/v2/test-image/"

    # fake the first initial response with problems
    responses.add(
        responses.POST,
        base_url + "blobs/uploads/",
        status=202,
        headers={
            "Location": "test-next-url",
            "Range": "9-9"
        },
    )

    # call!
    msg = "Server error: bad range received"
    with pytest.raises(CommandError, match=msg):
        ocireg.upload_blob("test-filepath", 8, "test-digest")
Exemple #25
0
def test_ociregistry_upload_manifest_v2(responses, caplog):
    """Upload a V2 manifest."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")
    ocireg = OCIRegistry("https://fakereg.com", "test-image")

    url = "https://fakereg.com/v2/test-image/manifests/test-reference"
    responses.add(responses.PUT, url, status=201)

    # try it
    raw_manifest_data = "test-manifest"
    ocireg.upload_manifest(raw_manifest_data, "test-reference")

    # check logs
    log_lines = [rec.message for rec in caplog.records]
    assert "Uploading manifest with reference test-reference" in log_lines
    assert "Manifest uploaded OK" in log_lines

    # check header and data sent
    assert responses.calls[0].request.headers[
        "Content-Type"] == MANIFEST_V2_MIMETYPE
    assert responses.calls[0].request.body == raw_manifest_data.encode("ascii")
Exemple #26
0
def test_ociregistry_upload_blob_bad_final_digest(tmp_path, responses):
    """Bad digest from server after closing the upload."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    base_url = "https://fakereg.com/v2/test-image/"

    # fake the first initial response
    pump_url_1 = base_url + "fakeurl-1"
    responses.add(
        responses.POST,
        base_url + "blobs/uploads/",
        status=202,
        headers={
            "Location": pump_url_1,
            "Range": "0-0"
        },
    )

    # and the intermediate one
    pump_url_2 = base_url + "fakeurl-2"
    responses.add(responses.PATCH,
                  pump_url_1,
                  status=202,
                  headers={"Location": pump_url_2})

    # finally, the closing url, bad digest
    responses.add(
        responses.PUT,
        base_url + "fakeurl-2&digest=test-digest",
        status=201,
        headers={"Docker-Content-Digest": "somethingelse"},
    )

    # prepare a fake content
    bytes_source = tmp_path / "testfile"
    bytes_source.write_text("abcdefgh")

    # call!
    msg = "Server error: the upload is corrupted"
    with pytest.raises(CommandError, match=msg):
        ocireg.upload_blob(bytes_source, 8, "test-digest")
def test_ociregistry_upload_blob_bad_upload_range(responses):
    """Received a broken range info."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    base_url = "https://fakereg.com/v2/test-image/"

    # fake the first initial response with problems
    responses.add(
        responses.POST,
        base_url + "blobs/uploads/",
        status=202,
        headers={
            "Location": "test-next-url",
            "Range": "9-9"
        },
    )

    # call!
    with pytest.raises(CraftError) as cm:
        ocireg.upload_blob("test-filepath", 8, "test-digest")
    error = cm.value
    assert str(error) == "Server error: bad range received"
    assert error.details == "Range='9-9'"
Exemple #28
0
def test_hit_simple_initial_auth_ok(caplog, responses):
    """Simple GET with auth working at once."""
    caplog.set_level(logging.DEBUG, logger="charmcraft")

    # set the Registry with an initial token
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # fake a 200 response
    responses.add(responses.GET, "https://fakereg.com/api/stuff")

    # try it
    response = ocireg._hit("GET", "https://fakereg.com/api/stuff")
    assert response == responses.calls[0].response

    # verify it authed ok
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    assert sent_auth_header == "Bearer some auth token"

    # logged what it did
    expected = "Hitting the registry: GET https://fakereg.com/api/stuff"
    assert [expected] == [rec.message for rec in caplog.records]
Exemple #29
0
def test_ociregistry_upload_blob_bad_response_closing(tmp_path, responses):
    """Bad response from the server when closing the upload."""
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    base_url = "https://fakereg.com/v2/test-image/"

    # fake the first initial response
    pump_url_1 = base_url + "fakeurl-1"
    responses.add(
        responses.POST,
        base_url + "blobs/uploads/",
        status=202,
        headers={
            "Location": pump_url_1,
            "Range": "0-0"
        },
    )

    # and the intermediate one
    pump_url_2 = base_url + "fakeurl-2"
    responses.add(responses.PATCH,
                  pump_url_1,
                  status=202,
                  headers={"Location": pump_url_2})

    # finally, the closing url, crashing
    responses.add(responses.PUT,
                  base_url + "fakeurl-2&digest=test-digest",
                  status=502)

    # prepare a fake content
    bytes_source = tmp_path / "testfile"
    bytes_source.write_text("abcdefgh")

    # call!
    msg = r"Wrong status code from server \(expected=201, got=502\).*"
    with pytest.raises(CommandError, match=msg):
        ocireg.upload_blob(bytes_source, 8, "test-digest")
Exemple #30
0
def test_hit_simple_re_auth_ok(responses):
    """Simple GET but needing to re-authenticate."""
    # set the Registry
    ocireg = OCIRegistry("https://fakereg.com", "test-image")
    ocireg.auth_token = "some auth token"

    # need to set up two responses!
    # - the 401 response with the proper info to re-auth
    # - the request that actually works
    headers = {
        "Www-Authenticate":
        ('Bearer realm="https://auth.fakereg.com/token",'
         'service="https://fakereg.com",scope="repository:library/stuff:pull"')
    }
    responses.add(responses.GET,
                  "https://fakereg.com/api/stuff",
                  headers=headers,
                  status=401)
    responses.add(responses.GET, "https://fakereg.com/api/stuff")

    # try it, isolating the re-authentication (tested separatedly above)
    with patch.object(ocireg, "_authenticate") as mock_auth:
        mock_auth.return_value = "new auth token"
        response = ocireg._hit("GET", "https://fakereg.com/api/stuff")
    assert response == responses.calls[1].response
    mock_auth.assert_called_with({
        "realm": "https://auth.fakereg.com/token",
        "scope": "repository:library/stuff:pull",
        "service": "https://fakereg.com",
    })

    # verify it authed ok both times, with corresponding tokens, and that it stored the new one
    sent_auth_header = responses.calls[0].request.headers.get("Authorization")
    assert sent_auth_header == "Bearer some auth token"
    sent_auth_header = responses.calls[1].request.headers.get("Authorization")
    assert sent_auth_header == "Bearer new auth token"
    assert ocireg.auth_token == "new auth token"