diff --git a/Makefile b/Makefile index 4bc93d8..331de1e 100644 --- a/Makefile +++ b/Makefile @@ -14,13 +14,8 @@ vendorize: # Checkout hook not available locally, so make a command hook instead cp local-pipeline/plugins/perforce/hooks/checkout local-pipeline/plugins/perforce/hooks/command -f -# p4d: export P4SSLDIR=sslkeys p4d: clean_p4d unzip python/fixture/server.zip -d python/fixture/server/ - # mkdir python/fixture/server/sslkeys - # chmod 700 python/fixture/server/sslkeys - # p4d -r python/fixture/server -Gc - # p4d -r python/fixture/server -Gf p4d -r python/fixture/server -p 1666 & clean_p4d: diff --git a/README.md b/README.md index 8828d6f..b013501 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ Override configuration at the User Environment level. May be overridden by P4CON See [p4 set](https://www.perforce.com/manuals/cmdref/Content/CmdRef/p4_set.html?Highlight=precedence) for more on system variables and precedence. +#### `fingerprint` (optional, string) + +Supply a trusted p4 server fingerprint to ensure the server the client connects to has not been MITM'd. + #### `stream` (optional, string) Which p4 stream to sync, e.g. `//dev/minimal`. Can be overridden by `view`. @@ -165,11 +169,11 @@ Run `dev/setup_env_osx.sh` Python [virtualenv](https://docs.python.org/3/tutorial/venv.html) `.dev-venv` for running tests will be created at repo root. -Run the `test_fixture` unit test to check everything is setup correctly: +Run the `test_server_fixture` unit test to check everything is setup correctly: ```bash source .dev-venv/bin/activate -pytest python/test_perforce.py -k test_fixture +pytest python/test_perforce.py -k test_server_fixture ``` ### Linux/Windows @@ -180,7 +184,7 @@ TBC, feedback welcome. Making changes to `python/` -* Read implementation of `test_fixture` in `test_perforce.py` +* Read implementation of `test_server_fixture` in `test_perforce.py` * Write unit test in `test_perforce.py`, optionally making changes to the test fixture if required * Implement new functionality * Iterate via unit test diff --git a/hooks/pre-checkout b/hooks/pre-checkout index e5b26ec..c2cc777 100755 --- a/hooks/pre-checkout +++ b/hooks/pre-checkout @@ -26,4 +26,4 @@ if [[ "${BUILDKITE_PLUGIN_PERFORCE_SHARE_WORKSPACE}" == true ]] ; then PERFORCE_CHECKOUT_PATH="${BUILDKITE_BUILD_CHECKOUT_PATH}/../../${SANITIZED_STREAM}" export BUILDKITE_BUILD_CHECKOUT_PATH="${PERFORCE_CHECKOUT_PATH}" echo "Changed BUILDKITE_BUILD_CHECKOUT_PATH to ${PERFORCE_CHECKOUT_PATH}" -fi \ No newline at end of file +fi diff --git a/plugin.yml b/plugin.yml index 79dc57b..0a3494a 100644 --- a/plugin.yml +++ b/plugin.yml @@ -27,5 +27,7 @@ configuration: type: bool sync: type: array + fingerprint: + type: string view: type: string diff --git a/python/buildkite.py b/python/buildkite.py index 59e7cb4..23d458c 100644 --- a/python/buildkite.py +++ b/python/buildkite.py @@ -40,7 +40,7 @@ def list_from_env_array(var): break result.append(elem) i += 1 - + return result def get_config(): @@ -52,12 +52,12 @@ def get_config(): conf['parallel'] = os.environ.get('BUILDKITE_PLUGIN_PERFORCE_PARALLEL') or 0 conf['client_options'] = os.environ.get('BUILDKITE_PLUGIN_PERFORCE_CLIENT_OPTIONS') conf['client_type'] = os.environ.get('BUILDKITE_PLUGIN_PERFORCE_CLIENT_TYPE') + conf['fingerprint'] = list_from_env_array('BUILDKITE_PLUGIN_PERFORCE_FINGERPRINT') if 'BUILDKITE_PLUGIN_PERFORCE_ROOT' in os.environ and not __LOCAL_RUN__: raise Exception("Custom P4 root is for use in unit tests only") conf['root'] = os.environ.get('BUILDKITE_PLUGIN_PERFORCE_ROOT') or os.environ.get('BUILDKITE_BUILD_CHECKOUT_PATH') - # Coerce view into pairs of [depot client] paths view_parts = conf['view'].split(' ') assert (len(view_parts) % 2) == 0, "Invalid view format" @@ -109,7 +109,7 @@ def set_build_changelist(changelist): """Set a shelved change that should be used instead of the user-supplied one""" if set_metadata(__SHELVED_METADATA__, changelist) and should_backup_changelists(): subprocess.call([ - 'buildkite-agent', 'annotate', + 'buildkite-agent', 'annotate', __SHELVED_ANNOTATION__.format(**{ 'original': get_users_changelist(), 'copy': changelist, @@ -133,7 +133,7 @@ def get_build_revision(): if revision.startswith('@') or revision.startswith('#'): return revision # Unable to establish a concrete revision for the build - return None + return None def set_build_revision(revision): """Set the p4 revision for following jobs in this build""" diff --git a/python/fixture/insecure-ssl/certificate.txt b/python/fixture/insecure-ssl/certificate.txt new file mode 100644 index 0000000..2710826 --- /dev/null +++ b/python/fixture/insecure-ssl/certificate.txt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAwIBATANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEDAOBgNVBAcMB0FsYW1lZGExHjAcBgNVBAoMFVBlcmZvcmNl +IEF1dG9nZW4gQ2VydDEfMB0GA1UEAwwWUGV0ZXJzLU1CUC0yLmZyaXR6LmJveDAe +Fw0yMDEwMjAxNTI1MTlaFw0yMjEwMjAxNTI1MTlaMG0xCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJDQTEQMA4GA1UEBwwHQWxhbWVkYTEeMBwGA1UECgwVUGVyZm9yY2Ug +QXV0b2dlbiBDZXJ0MR8wHQYDVQQDDBZQZXRlcnMtTUJQLTIuZnJpdHouYm94MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzqwbTJTvARGzfseVU5qKrxUQ +LZMs5PjoMUxhY+nqHXeWIN0BZlAuG2feO3qGnWHbVJf/2YFxrqvDfDTUWaYIrbsg +U2NeZcW9xrKm+0BllLCYkf12zDtc58MUMfgUuQSNAiPaYN/xztQ+Ief1fj2VxpiN +fkxCeVrk5R4ccbrfOV39/2Dz1Zx+g6F9o4c1xPmyS+DpYtULIduYq2ERnupOs7x/ +qQMwtPJZDQbVChwYdae5L1pqc+gHHgFwDs02FL6cxQjPSaUXl8hvx4/ke55wp9f6 +r4W7GljjFcUCzXYjefCMX1X9kUjI5AAFZ+yPgzxSRjnjjew4KtVWVRom29/tlQID +AQABMA0GCSqGSIb3DQEBBQUAA4IBAQC2sujXI7RbaazWjbGXzSZIHN7PGaqxMQIn +RP6AqlW3wvO0J29gbtSv4VmuH9z4EkgNISeSeAWUrY+YCdJYPQpj1kdiwZpxT3M5 +P8f6IrasIznUkgqmaOKjifoGTshhGQ7TtbQY2kFFyCNVI4749F/rniHcr9ELazZq +p06cwKLjSDulFTWO8MxBrPyh6UnhTfMHWUYjt9pGGEhTMis5ilwO/qaBUvCxaQKa +CIBvCQFnjkzdQAb8CJ9EwDpOI1VM0/Pf6FJPDJKaQ8RcF1b7IjT2RE27KXe3k0xQ +Ebz3gwplFQ+wGJVWCT9wpS+HQGCsJBKQF2u9P7PEQWMvlNEBWk+y +-----END CERTIFICATE----- diff --git a/python/fixture/insecure-ssl/privatekey.txt b/python/fixture/insecure-ssl/privatekey.txt new file mode 100644 index 0000000..ae6ebfd --- /dev/null +++ b/python/fixture/insecure-ssl/privatekey.txt @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDOrBtMlO8BEbN+ +x5VTmoqvFRAtkyzk+OgxTGFj6eodd5Yg3QFmUC4bZ947eoadYdtUl//ZgXGuq8N8 +NNRZpgituyBTY15lxb3Gsqb7QGWUsJiR/XbMO1znwxQx+BS5BI0CI9pg3/HO1D4h +5/V+PZXGmI1+TEJ5WuTlHhxxut85Xf3/YPPVnH6DoX2jhzXE+bJL4Oli1Qsh25ir +YRGe6k6zvH+pAzC08lkNBtUKHBh1p7kvWmpz6AceAXAOzTYUvpzFCM9JpReXyG/H +j+R7nnCn1/qvhbsaWOMVxQLNdiN58IxfVf2RSMjkAAVn7I+DPFJGOeON7Dgq1VZV +Gibb3+2VAgMBAAECggEAdf38d/Rvn4SjnbYEov6QPvUfj2V/NBqHNd4NnCVn6/ri +U1DaA7ezGyJp1jtVr3S268z73QnyBW865CalNal9OvKiufj5Y9FJT6+fdcKGPCW2 +dWLn+CHMIOVXGlAwRJE8kAQ4ISa5vwOdlW4A0loGsKNX5MtVCEPEeqp+QtAVsYHY +SeJyWw2QqlzMJ4i44zdxhHm3SpXmpIs8Rk0pee6Wta+EmS/kwExsRpgldz/L/DGw +VGUDIPdHbGYf58Aic9N7adKjzP8NOiyqOv1WyWqzH5Sz1rkDMElLJEk4SMY9P2VY +SwmTsdbtox2UDCgG7n1ZKryCDbcG/ppdIONR0Qx7AQKBgQD8GD54jjjp4E7rLBHG +dJaXILg4ZSSMAIeSsYkVCya8vzg84EIF5lbCfuhybYOJcB1KapDZ2/WiNzg3V2ur +GBxZtxvDGCPyI7PY+CVy0z39fKykvpe+O1Ai1FsUEutG6bX9/r4tIZTvgPzIXC5W +j8n9mJCCNBmLNkdqhr024jLCqQKBgQDR37oEMMiZBHED9Zd6SfsTUIV/bO6dDdRR +nEpG3C3c6gT783j8D65GyMPvA3anriSzzchBIXtXP0GOcoXfXNGrIDiZeCYo8rn4 +kitSP3ASw1Ydpi6QKCF32z8nCc9njdt7YZ7GO2lpcBKR+/ODnvWzCuSSYQprV9YJ +nW0Q5X2TDQKBgAEUtYfczD+sd4oomTbpnw+s0z1iqaJ0CiDF5BmT/6mFhF82cvIF +h8+zrZl4AL1hHq8H//D/MXFtnS8Xj92e79guoc7XVqgeIRJIFhkE8NoaY78dFhd5 +t6E+mdlfL2URcXdSVUxqPXI9clgFlSlH2ozcz5nPUWC4bdv2Ee+fTqppAoGAXyyu +Fqhoz7uL5NfC0doq2h9x6s9jhiV3W2sc4/WFduFJUVigTO5vgfoZoJJZhMEcM83m +OmMMpAwzln2o6BoXmxsJj89Evt0UKP1gV/QcxuV+cAOkqgsI4mmywelY/QT/u3wR +nKPkscP5J+qyC8ZSdddCwH7xUqyKi+GwTDFGOSECgYBghf4sow5Q31o+fhI2BSCE +/6AfQkaBSjzf1KZiZseU2Je8anRhnzUN5eo/dOgDuixp1jcHZolHJO1OxsyGbjMW +VWk20wWHl7iDb9cVIqC1ev5dVGw9KhOTbpETxPoFEMfMvQ4CAG6hTBcadCuXjUDJ +nee25LdY7l8t5qam/DNDLA== +-----END PRIVATE KEY----- diff --git a/python/fixture/insecure-ssl/readme.md b/python/fixture/insecure-ssl/readme.md new file mode 100644 index 0000000..a3404aa --- /dev/null +++ b/python/fixture/insecure-ssl/readme.md @@ -0,0 +1,14 @@ +# Insecure SSL + +Cert for use in unit tests. + +Do not use in production. + +Generated via: + +```bash +mkdir -p "python/fixture/insecure-ssl" +chmod 700 "python/fixture/insecure-ssl" +P4SSLDIR="python/fixture/insecure-ssl" p4d -Gc +P4SSLDIR="python/fixture/insecure-ssl" p4d -Gf +``` diff --git a/python/perforce.py b/python/perforce.py index 496879a..13cc254 100644 --- a/python/perforce.py +++ b/python/perforce.py @@ -16,7 +16,7 @@ class P4Repo: """A class for manipulating perforce workspaces""" def __init__(self, root=None, view=None, stream=None, sync=None, - client_options=None, client_type=None, parallel=0): + client_options=None, client_type=None, parallel=0, fingerprint=None): """ root: Directory in which to create the client workspace view: Client workspace mapping @@ -25,6 +25,7 @@ def __init__(self, root=None, view=None, stream=None, sync=None, client_options: Additional options to add to client. (e.g. allwrite) client_type: Type of client (writeable, readonly, partitioned) parallel: How many threads to use for parallel sync. + fingerprint: Acceptable fingerprint for a p4 server to have. """ self.root = os.path.abspath(root or '') self.stream = stream @@ -34,6 +35,7 @@ def __init__(self, root=None, view=None, stream=None, sync=None, self.client_options = client_options or '' self.client_type = client_type or 'writeable' self.parallel = parallel + self.fingerprint = fingerprint or '' self.created_client = False self.patchfile = os.path.join(self.root, 'patched.json') @@ -53,9 +55,18 @@ def __init__(self, root=None, view=None, stream=None, sync=None, logger.addHandler(handler) self.perforce.logger = logger self.perforce.connect() + if self.perforce.port.startswith('ssl'): - # TODO: Remove this and enforce prior provisioning of trusted fingerprints - self.perforce.run_trust('-y') + if self.fingerprint: + self.perforce.run_trust( + '-r', # Install a replacement fingerprint - will replace primary if this matches the server + '-i', # Install the specified fingerprint + self.fingerprint, + ) + else: + # Trust fingerprint from first contact with server + # If fingerprint changes, MITM attack is reported + self.perforce.run_trust('-y') def __del__(self): self.perforce.disconnect() @@ -195,7 +206,7 @@ def head_at_revision(self, revision): revision = labelinfo.get('Revision') or revision except P4Exception: # revision may be clientname, datespec or something else - # fallback to default behaviour + # fallback to default behaviour pass # Get last submitted change at revision spec @@ -281,7 +292,7 @@ def p4print_unshelve(self, changelist): whereinfo = self.perforce.run_where(depotfiles) depot_to_local = {item['depotFile']: item['path'] for item in whereinfo} - + # Flag these files as modified self._write_patched(list(depot_to_local.values())) @@ -319,7 +330,7 @@ def __init__(self, logger): OutputHandler.__init__(self) self.logger = logger self.sync_count = 0 - + def outputStat(self, stat): if 'depotFile' in stat: self.sync_count += 1 diff --git a/python/test_perforce.py b/python/test_perforce.py index c64a6a4..25ecaa0 100644 --- a/python/test_perforce.py +++ b/python/test_perforce.py @@ -19,7 +19,6 @@ __P4D_TIMEOUT__ = 30 # __P4D_TIMEOUT__ = None - def find_free_port(): """Find an open port that we could run a perforce server on""" # pylint: disable=no-member @@ -28,7 +27,6 @@ def find_free_port(): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return sock.getsockname()[1] - def run_p4d(p4port, from_zip=None): """Start a perforce server with the given hostname:port. Optionally unzip server state from a file @@ -47,20 +45,30 @@ def run_p4d(p4port, from_zip=None): zip_path = os.path.join(os.path.dirname(__file__), 'fixture', from_zip) with zipfile.ZipFile(zip_path) as archive: archive.extractall(tmpdir) + + p4ssldir = os.path.join(tmpdir, 'ssl') + p4trust = os.path.join(tmpdir, 'trust.txt') + shutil.copytree(os.path.join(os.path.dirname(__file__), 'fixture', 'insecure-ssl'), p4ssldir) + # Like a beautifully crafted work of art, p4d fails to start if permissions on the secrets are too open. + # https://www.perforce.com/manuals/v18.1/cmdref/Content/CmdRef/P4SSLDIR.html + os.chmod(p4ssldir, 0o700) + os.chmod(os.path.join(p4ssldir, 'privatekey.txt'), 0o600) + os.chmod(os.path.join(p4ssldir, 'certificate.txt'), 0o600) + os.environ['P4SSLDIR'] = p4ssldir + os.environ['P4TRUST'] = p4trust try: - subprocess.check_output(["p4d", "-r", tmpdir, "-p", str(p4port)], + subprocess.check_output(["p4d", "-r", tmpdir, "-p", p4port], timeout=__P4D_TIMEOUT__) except subprocess.TimeoutExpired: pass - @pytest.fixture def server(): """Start a p4 server in the background and return the address""" port = find_free_port() - Thread(target=partial(run_p4d, port, from_zip='server.zip'), daemon=True).start() + p4port = 'ssl:localhost:%s' % port + Thread(target=partial(run_p4d, p4port, from_zip='server.zip'), daemon=True).start() time.sleep(1) - p4port = 'localhost:%s' % port os.environ['P4PORT'] = p4port return p4port @@ -70,7 +78,6 @@ def tmpdir(): with tempfile.TemporaryDirectory(prefix="bk-p4-test-") as tmpdir: yield tmpdir - def store_server(repo, to_zip): """Zip up a server to use as a unit test fixture""" serverRoot = repo.info()['serverRoot'] @@ -82,12 +89,11 @@ def store_server(repo, to_zip): abs_path = os.path.join(root, filename) archive.write(abs_path, os.path.relpath(abs_path, serverRoot)) -def test_fixture(capsys, server): +def test_server_fixture(capsys, server): """Check that tests can start and connect to a local perforce server""" with capsys.disabled(): print('port:', server, 'user: carl') repo = P4Repo() - assert repo.info()['serverAddress'] == server # To change the fixture server, uncomment the line below with 'store_server' and put a breakpoint on it # Change __P4D_TIMEOUT__ to 'None' or an otherwise large amount of time @@ -99,7 +105,7 @@ def test_fixture(capsys, server): # Update validation code below to document the new server contents # store_server(repo, 'new_server.zip') - + # Validate contents of server fixture @HEAD depotfiles = [info['depotFile'] for info in repo.perforce.run_files('//...')] depotfile_to_content = {depotfile: repo.perforce.run_print(depotfile)[1] for depotfile in depotfiles} @@ -115,8 +121,8 @@ def test_fixture(capsys, server): submitted_changeinfo = {change["change"]: repo.perforce.run_describe(change["change"])[0] for change in submitted_changes} # Filter info to only contain relevant keys for submitted changes submitted_changeinfo = { - change: {key: info.get(key) - for key in ['depotFile', 'desc', 'action']} + change: {key: info.get(key) + for key in ['depotFile', 'desc', 'action']} for change, info in submitted_changeinfo.items() } assert submitted_changeinfo == { @@ -157,8 +163,8 @@ def test_fixture(capsys, server): shelved_changeinfo = {change["change"]: repo.perforce.run_describe('-S', change["change"])[0] for change in shelved_changes} # Filter info to only contain relevant keys for submitted changes shelved_changeinfo = { - change: {key: info.get(key) - for key in ['depotFile', 'desc', 'action']} + change: {key: info.get(key) + for key in ['depotFile', 'desc', 'action']} for change, info in shelved_changeinfo.items() } assert shelved_changeinfo == { @@ -183,7 +189,7 @@ def test_fixture(capsys, server): labels = repo.perforce.run_labels() # Filter info to only contain relevant keys labelinfo = { - label.get('label'): {key: label.get(key) + label.get('label'): {key: label.get(key) for key in ['Revision'] } for label in labels @@ -250,7 +256,7 @@ def test_checkout_stream(server, tmpdir): assert os.listdir(tmpdir) == [], "Workspace should be empty" repo.sync() with open(os.path.join(tmpdir, "file.txt")) as content: - assert content.read() == "Hello Stream World\n", "Unexpected content in workspace file" + assert content.read() == "Hello Stream World\n", "Unexpected content in workspace file" def test_checkout_label(server, tmpdir): """Test checking out at a specific label""" @@ -261,7 +267,7 @@ def test_checkout_label(server, tmpdir): repo.sync(revision="@my-label") with open(os.path.join(tmpdir, "file.txt")) as content: - assert content.read() == "Hello World\n", "Unexpected content in workspace file" + assert content.read() == "Hello World\n", "Unexpected content in workspace file" def test_readonly_client(server, tmpdir): """Test creation of a readonly client""" @@ -339,7 +345,6 @@ def test_unshelve(server, tmpdir): assert content.read() == "Hello World\n", "Unexpected content in workspace file" assert not os.path.exists(os.path.join(tmpdir, "newfile.txt")), "File unshelved for add was not deleted" - def test_p4print_unshelve(server, tmpdir): """Test unshelving a pending changelist by p4printing content into a file""" repo = P4Repo(root=tmpdir) @@ -389,7 +394,6 @@ def test_backup_shelve(server, tmpdir): with open(os.path.join(tmpdir, "file.txt")) as content: assert content.read() == "Goodbye World\n", "Unexpected content in workspace file" - def copytree(src, dst): """Shim to get around shutil.copytree requiring root dir to not exist""" for item in os.listdir(src): @@ -407,7 +411,7 @@ def test_client_migration(server, tmpdir): assert os.listdir(tmpdir) == [], "Workspace should be empty" synced = repo.sync() assert len(synced) > 0, "Didn't sync any files" - + with tempfile.TemporaryDirectory(prefix="bk-p4-test-") as second_client: copytree(tmpdir, second_client) # Client names include path on disk, so this creates a new unique client @@ -423,7 +427,7 @@ def test_stream_switching(server, tmpdir): assert set(os.listdir(tmpdir)) == set([ "file.txt", "file_2.txt", "p4config"]) with open(os.path.join(tmpdir, "file.txt")) as content: - assert content.read() == "Hello Stream World\n", "Unexpected content in workspace file" + assert content.read() == "Hello Stream World\n", "Unexpected content in workspace file" # Re-use the same checkout directory, but switch streams repo = P4Repo(root=tmpdir, stream='//stream-depot/dev') @@ -432,7 +436,7 @@ def test_stream_switching(server, tmpdir): assert set(os.listdir(tmpdir)) == set([ "file.txt", "p4config"]) # file_2.txt was de-synced with open(os.path.join(tmpdir, "file.txt")) as content: - assert content.read() == "Hello Stream World (dev)\n", "Unexpected content in workspace file" + assert content.read() == "Hello Stream World (dev)\n", "Unexpected content in workspace file" def test_stream_switching_migration(server, tmpdir): """Test stream-switching and client migration simultaneously""" @@ -442,7 +446,7 @@ def test_stream_switching_migration(server, tmpdir): assert set(os.listdir(tmpdir)) == set([ "file.txt", "file_2.txt", "p4config"]) with open(os.path.join(tmpdir, "file.txt")) as content: - assert content.read() == "Hello Stream World\n", "Unexpected content in workspace file" + assert content.read() == "Hello Stream World\n", "Unexpected content in workspace file" with tempfile.TemporaryDirectory(prefix="bk-p4-test-") as second_client: copytree(tmpdir, second_client) @@ -454,7 +458,33 @@ def test_stream_switching_migration(server, tmpdir): assert set(os.listdir(second_client)) == set([ "file.txt", "p4config"]) # file_2.txt was de-synced with open(os.path.join(second_client, "file.txt")) as content: - assert content.read() == "Hello Stream World (dev)\n", "Unexpected content in workspace file" + assert content.read() == "Hello Stream World (dev)\n", "Unexpected content in workspace file" + +# fingerprint here matches to the cert in the test fixture directory, and you can check that with +# P4SSLDIR=$(pwd)/python/fixture/insecure-ssl p4d -Gf +__LEGIT_P4_FINGERPRINT__ = '7A:10:F6:00:95:87:5B:2E:D4:33:AB:44:42:05:85:94:1C:93:2E:A2' + +def test_fingerprint_good(server, tmpdir): + """Test supplying the correct fingerprint""" + repo = P4Repo(root=tmpdir, fingerprint=__LEGIT_P4_FINGERPRINT__) + synced = repo.sync() + assert len(synced) > 0, "Didn't sync any files" + +def test_fingerprint_bad(server, tmpdir): + """Test supplying an incorrect fingerprint""" + repo = P4Repo(root=tmpdir, fingerprint='FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF') + with pytest.raises(Exception, match=r"The authenticity of '.+' can't be established"): + repo.sync() + +def test_fingerprint_changed(server, tmpdir): + """Test updating a fingerprint""" + repo = P4Repo(root=tmpdir, fingerprint='FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF') + with pytest.raises(Exception, match=r"The authenticity of '.*' can't be established"): + repo.sync() + + repo = P4Repo(root=tmpdir, fingerprint=__LEGIT_P4_FINGERPRINT__) + synced = repo.sync() + assert len(synced) > 0, "Didn't sync any files" # def test_live_server():