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
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]
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
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
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
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)
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)
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
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]
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
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")
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")
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")
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)
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")
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")
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'"
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]
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")
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"