Skip to content

Commit

Permalink
Stop self-update picking releases that are not the latest
Browse files Browse the repository at this point in the history
We will be using pre-release tags for testing, which would be listed later than the latest tag
  • Loading branch information
andytson-inviqa committed Feb 24, 2023
1 parent 9a527f2 commit 519899c
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/Updater/Plugin/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private function action()
}

try {
$this->updater->update(BaseApplication::getVersion(), $pharPath);
$this->updater->updateLatest(BaseApplication::getVersion(), $pharPath);
} catch (NoUpdateAvailableException $e) {
echo sprintf('You are already running the latest version of workspace: %s', $e->getCurrentVersion()) . PHP_EOL;
exit(1);
Expand Down
32 changes: 15 additions & 17 deletions src/Updater/Updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,28 @@ public function __construct(string $apiUrl, ?Output $output = null)
$this->output = $output ?: new StdOutput();
}

public function update(string $currentVersion, string $targetPath)
public function updateLatest(string $currentVersion, string $targetPath)
{
if (empty($currentVersion)) {
throw new NoVersionDeterminedException();
}

$latest = $this->getLatestRelease();
if (!$latest->isMoreRecentThan($currentVersion)) {
throw new NoUpdateAvailableException($currentVersion);
}
$this->doUpdate($currentVersion, $latest, $targetPath);
}

private function doUpdate(string $currentVersion, Release $release, string $targetPath)
{
if (empty($currentVersion)) {
throw new NoVersionDeterminedException();
}

$temp = tempnam(sys_get_temp_dir(), 'workspace-update-') . '.phar';

try {
$this->output->infof('Downloading new version (%s) from %s', $latest->getVersion(), $latest->getUrl());
$releaseData = @file_get_contents($latest->getUrl());
$this->output->infof('Downloading new version (%s) from %s', $release->getVersion(), $release->getUrl());
$releaseData = @file_get_contents($release->getUrl());
if ($releaseData === false) {
throw new \RuntimeException(sprintf('Unable to download latest release at %s', $latest->getUrl()), self::CODE_ERR_FETCHING_NEXT_RELEASE);
throw new \RuntimeException(sprintf('Unable to download latest release at %s', $release->getUrl()), self::CODE_ERR_FETCHING_NEXT_RELEASE);
}

$this->output->infof('Writing to %s', $temp);
Expand All @@ -67,18 +71,12 @@ public function update(string $currentVersion, string $targetPath)
private function getLatestRelease(): Release
{
try {
$releases = file_get_contents($this->apiUrl, false, $this->createStreamContext());
$release = file_get_contents($this->apiUrl . '/latest', false, $this->createStreamContext());
} catch (\Throwable $e) {
throw new \RuntimeException('Error fetching releases from GitHub.', self::CODE_ERR_FETCHING_RELEASES);
}

$releases = json_decode($releases);

if (count($releases) === 0) {
throw new \RuntimeException('No releases present in the GitHub API response.', self::CODE_NO_RELEASES);
throw new \RuntimeException('Error fetching latest release from GitHub.', self::CODE_ERR_FETCHING_RELEASES);
}

$latest = $releases[0];
$latest = json_decode($release);

return new Release($latest->assets[0]->browser_download_url, $latest->tag_name);
}
Expand Down
62 changes: 33 additions & 29 deletions tests/Test/Updater/UpdaterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@ class UpdaterTest extends TestCase
{
public static function tearDownAfterClass(): void
{
array_map('unlink', glob(__DIR__ . '/fixtures/generated/*.*'));
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(__DIR__ . '/fixtures/generated', \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
if ($file->isDir()) {
rmdir($file);
} else {
unlink($file);
}
}
}

public static function setUpBeforeClass(): void
Expand All @@ -28,76 +38,70 @@ public function exceptionThrownWhenErrorFetchingReleases()
$this->expectExceptionCode(Updater::CODE_ERR_FETCHING_RELEASES);

$updater = new Updater(__DIR__ . '/fixtures/foo.json', new NullOutput());
$updater->update('1.0.0', '');
}

/** @test */
public function exceptionThrownWhenThereAreNoReleases()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionCode(Updater::CODE_NO_RELEASES);

$updater = new Updater(__DIR__ . '/fixtures/empty-releases.json');
$updater->update('1.0.0', '');
$updater->updateLatest('1.0.0', '');
}

/** @test */
public function exceptionThrownWhenAlreadyOnLatest()
{
$this->prepareReleasesFixture('latest.json', '', '1.0.0');
$this->prepareLatestFixture('valid/latest', '', '1.0.0');
$this->expectException(NoUpdateAvailableException::class);

$updater = new Updater(__DIR__ . '/fixtures/generated/latest.json', new NullOutput());
$updater->update('1.0.0', '');
$updater = new Updater(__DIR__ . '/fixtures/generated/valid', new NullOutput());
$updater->updateLatest('1.0.0', '');
}

/** @test */
public function exceptionThrownWhenCurrentVersionIsEmpty()
{
$this->prepareReleasesFixture('latest.json', '', '1.0.0');
$this->prepareLatestFixture('valid/latest', '', '1.0.0');
$this->expectException(NoVersionDeterminedException::class);

$updater = new Updater(__DIR__ . '/fixtures/generated/latest.json', new NullOutput());
$updater->update('', '');
$updater = new Updater(__DIR__ . '/fixtures/generated/valid', new NullOutput());
$updater->updateLatest('', '');
}

/** @test */
public function exceptionThrownWhenOnMoreRecentVersion()
{
$this->prepareReleasesFixture('older.json', '', '1.0.0');
$this->prepareLatestFixture('older/latest', '', '1.0.0');
$this->expectException(NoUpdateAvailableException::class);

$updater = new Updater(__DIR__ . '/fixtures/generated/older.json', new NullOutput());
$updater->update('1.1.0', '');
$updater = new Updater(__DIR__ . '/fixtures/generated/older', new NullOutput());
$updater->updateLatest('1.1.0', '');
}

/** @test */
public function exceptionThrownWhenNextReleaseCannotBeDownloaded()
{
$this->prepareReleasesFixture('invalid-release.json', __DIR__ . '/foo.baz.bar', '1.0.0');
$this->prepareLatestFixture('invalid-release/latest', __DIR__ . '/foo.baz.bar', '1.0.0');
$this->expectException(\RuntimeException::class);
$this->expectExceptionCode(Updater::CODE_ERR_FETCHING_NEXT_RELEASE);

$updater = new Updater(__DIR__ . '/fixtures/generated/invalid-release.json', new NullOutput());
$updater->update('0.9.0', '');
$updater = new Updater(__DIR__ . '/fixtures/generated/invalid-release', new NullOutput());
$updater->updateLatest('0.9.0', '');
}

/** @test */
public function downloadsLatestReleaseToDesiredTargetPath()
{
$this->prepareFakePhar();
$this->prepareReleasesFixture('valid.json', __DIR__ . '/fixtures/generated/fake.phar', '1.0.0');
$this->prepareLatestFixture('valid/latest', __DIR__ . '/fixtures/generated/fake.phar', '1.0.0');

$temp = sys_get_temp_dir() . '/test-ws-download';
$updater = new Updater(__DIR__ . '/fixtures/generated/valid.json', new NullOutput());
$updater->update('0.9.0', $temp);
$updater = new Updater(__DIR__ . '/fixtures/generated/valid', new NullOutput());
$updater->updateLatest('0.9.0', $temp);

$this->assertFileExists($temp);
}

private function prepareReleasesFixture(string $name, string $releasePath, string $version): void
private function prepareLatestFixture(string $name, string $releasePath, string $version): void
{
$contents = file_get_contents(__DIR__ . '/fixtures/tpl/releases.json');
$dir = dirname(__DIR__ . '/fixtures/generated/' . $name);
if (!file_exists($dir)) {
mkdir($dir);
}
$contents = file_get_contents(__DIR__ . '/fixtures/tpl/latest.json');
$contents = str_replace(['%%browserDownloadUrl%%', '%%versionTag%%'], [$releasePath, $version], $contents);

file_put_contents(__DIR__ . '/fixtures/generated/' . $name, $contents);
Expand Down
74 changes: 74 additions & 0 deletions tests/Test/Updater/fixtures/tpl/latest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"url": "https://api.github.com/repos/my127/workspace/releases/43190729",
"assets_url": "https://api.github.com/repos/my127/workspace/releases/43190729/assets",
"upload_url": "https://uploads.github.com/repos/my127/workspace/releases/43190729/assets{?name,label}",
"html_url": "https://github.com/my127/workspace/releases/tag/%%versionTag%%",
"id": 43190729,
"author": {
"login": "joe",
"id": 9999,
"node_id": "MDQ6VXNlcjE1NTQ3MDk=",
"avatar_url": "https://avatars.githubusercontent.com/u/1554709?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/joe",
"html_url": "https://github.com/joe",
"followers_url": "https://api.github.com/users/joe/followers",
"following_url": "https://api.github.com/users/joe/following{/other_user}",
"gists_url": "https://api.github.com/users/joe/gists{/gist_id}",
"starred_url": "https://api.github.com/users/joe/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/joe/subscriptions",
"organizations_url": "https://api.github.com/users/joe/orgs",
"repos_url": "https://api.github.com/users/joe/repos",
"events_url": "https://api.github.com/users/joe/events{/privacy}",
"received_events_url": "https://api.github.com/users/joe/received_events",
"type": "User",
"site_admin": false
},
"node_id": "MDc6UmVsZWFzZTQzMTkwNzI5",
"tag_name": "%%versionTag%%",
"target_commitish": "1.x",
"name": "%%versionTag%%",
"draft": false,
"prerelease": true,
"created_at": "2021-05-19T06:18:24Z",
"published_at": "2021-05-19T06:24:32Z",
"assets": [
{
"url": "https://api.github.com/repos/my127/workspace/releases/assets/37159196",
"id": 37159196,
"node_id": "MDEyOlJlbGVhc2VBc3NldDM3MTU5MTk2",
"name": "ws",
"label": null,
"uploader": {
"login": "joe",
"id": 1554709,
"node_id": "MDQ6VXNlcjE1NTQ3MDk=",
"avatar_url": "https://avatars.githubusercontent.com/u/1554709?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/joe",
"html_url": "https://github.com/joe",
"followers_url": "https://api.github.com/users/joe/followers",
"following_url": "https://api.github.com/users/joe/following{/other_user}",
"gists_url": "https://api.github.com/users/joe/gists{/gist_id}",
"starred_url": "https://api.github.com/users/joe/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/joe/subscriptions",
"organizations_url": "https://api.github.com/users/joe/orgs",
"repos_url": "https://api.github.com/users/joe/repos",
"events_url": "https://api.github.com/users/joe/events{/privacy}",
"received_events_url": "https://api.github.com/users/joe/received_events",
"type": "User",
"site_admin": false
},
"content_type": "application/octet-stream",
"state": "uploaded",
"size": 2944641,
"download_count": 20,
"created_at": "2021-05-19T06:22:50Z",
"updated_at": "2021-05-19T06:22:54Z",
"browser_download_url": "%%browserDownloadUrl%%"
}
],
"tarball_url": "https://api.github.com/repos/my127/workspace/tarball/%%versionTag%%",
"zipball_url": "https://api.github.com/repos/my127/workspace/zipball/%%versionTag%%",
"body": "## [%%versionTag%%](https://github.com/my127/workspace/tree/%%versionTag%%) (2021-05-19)\r\n\r\n[Full Changelog](https://github.com/my127/workspace/compare/0.1.3...%%versionTag1%%)\r\n\r\n**Implemented enhancements:**\r\n\r\n- Add a cheatsheet [\\#83](https://github.com/my127/workspace/pull/83) ([rgpjones](https://github.com/rgpjones))\r\n- Add poweroff command [\\#82](https://github.com/my127/workspace/pull/82) ([joe](https://github.com/joe))\r\n- Add before/after overlay events and match with before for prepare [\\#78](https://github.com/my127/workspace/pull/78) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Improve integration test suite [\\#77](https://github.com/my127/workspace/pull/77) ([dantleech](https://github.com/dantleech))\r\n- Add PHP 8 support [\\#68](https://github.com/my127/workspace/pull/68) ([elvetemedve](https://github.com/elvetemedve))\r\n- Add config dump command [\\#67](https://github.com/my127/workspace/pull/67) ([hgajjar](https://github.com/hgajjar))\r\n- Add Jaeger daemon [\\#66](https://github.com/my127/workspace/pull/66) ([joe](https://github.com/joe))\r\n- Add more default symfony expression functions [\\#56](https://github.com/my127/workspace/pull/56) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Update symfony components and twig to latest minor version [\\#55](https://github.com/my127/workspace/pull/55) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Upgrade to mailhog image v1.0.1 [\\#54](https://github.com/my127/workspace/pull/54) ([joe](https://github.com/joe))\r\n- Fix multiple argument run/passthru escaping [\\#53](https://github.com/my127/workspace/pull/53) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Make commands and function errors debuggable by saying what name they are [\\#51](https://github.com/my127/workspace/pull/51) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Add missing Bash interpreter page [\\#49](https://github.com/my127/workspace/pull/49) ([opdavies](https://github.com/opdavies))\r\n- Misc: license as MIT [\\#47](https://github.com/my127/workspace/pull/47) ([dcole-inviqa](https://github.com/dcole-inviqa))\r\n- Note that curl is required [\\#46](https://github.com/my127/workspace/pull/46) ([joe](https://github.com/joe))\r\n- Add an after\\('harness.prepare'\\) event [\\#44](https://github.com/my127/workspace/pull/44) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Test on PHP 7.4 [\\#40](https://github.com/my127/workspace/pull/40) ([joe](https://github.com/joe))\r\n\r\n**Fixed bugs:**\r\n\r\n- Add requirement to use prefix space to avoid secrets in shell history [\\#65](https://github.com/my127/workspace/pull/65) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n- Handle sidekick errexiting correctly [\\#58](https://github.com/my127/workspace/pull/58) ([andytson-inviqa](https://github.com/andytson-inviqa))\r\n\r\n**Closed issues:**\r\n\r\n- Service commands provide no feedback if command not found [\\#62](https://github.com/my127/workspace/issues/62)\r\n- Support for self-update? [\\#60](https://github.com/my127/workspace/issues/60)\r\n- Executing a ws command that does not exist should return a non-zero exit code [\\#45](https://github.com/my127/workspace/issues/45)\r\n- Support for multiple harnesses to one workspace [\\#23](https://github.com/my127/workspace/issues/23)\r\n\r\n## Installation instructions\r\n```bash\r\ncurl --output ./ws --location https://github.com/my127/workspace/releases/download/%%versionTag1%%/ws\r\nchmod +x ws && sudo mv ws /usr/local/bin/ws\r\n```"
}

0 comments on commit 519899c

Please sign in to comment.