Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support release commits (and improve tests) #62

Merged
merged 11 commits into from
Oct 14, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* Add support to load release variable from environment ([#40](https://github.com/getsentry/sentry-dart-plugin/pull/40))
* Download Sentry CLI on first run ([#49](https://github.com/getsentry/sentry-dart-plugin/pull/49))
* Support release commits ([#62](https://github.com/getsentry/sentry-dart-plugin/pull/62))
vaind marked this conversation as resolved.
Show resolved Hide resolved

### Dependencies

Expand Down
59 changes: 36 additions & 23 deletions integration-test/integration-test-server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
apiOrg = 'sentry-sdks'
apiProject = 'sentry-dart-plugin'
uri = urlparse(sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8000')
version='1.1.0'
appIdentifier='project'
version = '1.1.0'
appIdentifier = 'project'


class Handler(BaseHTTPRequestHandler):
body = None

def do_GET(self):
self.start_response(HTTPStatus.OK)
self.start_response()

if self.path == "/STOP":
print("HTTP server stopping!")
threading.Thread(target=self.server.shutdown).start()
Expand All @@ -32,17 +33,17 @@ def do_GET(self):
'"accept":["debug_files","release_files","pdbs","sources","bcsymbolmaps"]}')
elif self.isApi('/api/0/organizations/{}/repos/?cursor='.format(apiOrg)):
self.writeJSONFile("assets/repos.json")
elif self.isApi('/api/0/organizations/{}/releases/{}/previous-with-commits/'.format(apiOrg, version)):
self.writeJSONFile("assets/release.json")
elif self.isApi('/api/0/organizations/{}/releases/{}@{}/previous-with-commits/'.format(apiOrg, appIdentifier, version)):
self.writeJSON('{ }')
elif self.isApi('/api/0/projects/{}/{}/releases/{}/files/?cursor='.format(apiOrg, apiProject, version)):
self.writeJSONFile("assets/artifacts.json")
else:
self.end_headers()
self.writeNoApiMatchesError()

self.flushLogs()

def do_POST(self):
self.start_response(HTTPStatus.OK)
self.start_response()

if self.isApi('api/0/projects/{}/{}/files/difs/assemble/'.format(apiOrg, apiProject)):
# Request body example:
Expand Down Expand Up @@ -77,36 +78,37 @@ def do_POST(self):
self.writeJSONFile("assets/debug-info-files.json")
elif self.isApi('/api/0/projects/{}/{}/files/dsyms/associate/'.format(apiOrg, apiProject)):
self.writeJSONFile("assets/associate-dsyms-response.json")
elif self.isApi('/api/0/projects/{}/{}/reprocessing/'.format(apiOrg, apiProject)):
self.writeJSON('{ }')
elif self.isApi('api/0/organizations/{}/chunk-upload/'.format(apiOrg)):
self.writeJSON('{ }')
else:
self.end_headers()
self.writeNoApiMatchesError()

self.flushLogs()

def do_PUT(self):
self.start_response(HTTPStatus.OK)
self.start_response()

if self.isApi('/api/0/organizations/{}/releases/{}/'.format(apiOrg, version)):
if self.isApi('/api/0/organizations/{}/releases/{}@{}/'.format(apiOrg, appIdentifier, version)):
self.writeJSONFile("assets/release.json")
if self.isApi('/api/0/projects/{}/{}/releases/{}@{}/'.format(apiOrg, apiProject, appIdentifier, version)):
elif self.isApi('/api/0/projects/{}/{}/releases/{}@{}/'.format(apiOrg, apiProject, appIdentifier, version)):
self.writeJSONFile("assets/release.json")
else:
self.end_headers()
self.writeNoApiMatchesError()

self.flushLogs()

def start_response(self, code):
def start_response(self):
self.body = None
self.log_request(code)
self.send_response_only(code)
self.log_request()

def log_request(self, code=None, size=None):
if isinstance(code, HTTPStatus):
code = code.value
def log_request(self, size=None):
body = self.body = self.requestBody()
if body:
body = self.body[0:min(1000, len(body))]
self.log_message('"%s" %s %s%s',
self.requestline, str(code), "({} bytes)".format(size) if size else '', body)
self.log_message('"%s" %s%s',
self.requestline, "({} bytes)".format(size) if size else '', body)

# Note: this may only be called once during a single request - can't `.read()` the same stream again.
def requestBody(self):
Expand All @@ -125,15 +127,26 @@ def isApi(self, api: str):
return True
return False

def writeNoApiMatchesError(self):
err = "Error: no API matched {} '{}'".format(self.command, self.path)
self.log_error(err)
self.writeResponse(HTTPStatus.NOT_IMPLEMENTED,
"text/plain", err)

def writeJSONFile(self, file_name: str):
json_file = open(file_name, "r")
self.writeJSON(json_file.read())
json_file.close()

def writeJSON(self, string: str):
self.send_header("Content-type", "application/json")
self.writeResponse(HTTPStatus.OK, "application/json", string)

def writeResponse(self, code: HTTPStatus, type: str, body: str):
self.send_response_only(code)
self.send_header("Content-type", type)
self.send_header("Content-Length", len(body))
self.end_headers()
self.wfile.write(str.encode(string))
self.wfile.write(str.encode(body))

def flushLogs(self):
sys.stdout.flush()
Expand Down
78 changes: 48 additions & 30 deletions lib/sentry_dart_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@ class SentryDartPlugin {
Log.info('uploadNativeSymbols is disabled.');
}

_executeNewRelease();

if (_configuration.uploadSourceMaps) {
_executeCliForSourceMaps();
} else {
Log.info('uploadSourceMaps is disabled.');
}

if (_configuration.commits.toLowerCase() != 'false') {
_executeSetCommits();
} else {
Log.info('Commit integration is disabled.');
}

_executeFinalizeRelease();
} on ExitError catch (e) {
return e.code;
}
Expand All @@ -54,14 +64,14 @@ class SentryDartPlugin {

params.add('upload-dif');

_addOrgAndProject(params);

if (_configuration.includeNativeSources) {
params.add('--include-sources');
} else {
Log.info('includeNativeSources is disabled, not uploading sources.');
}

_addOrgAndProject(params);

params.add(_configuration.buildFilesFolder);

_addWait(params);
Expand All @@ -71,36 +81,52 @@ class SentryDartPlugin {
Log.taskCompleted(taskName);
}

void _executeCliForSourceMaps() {
const taskName = 'uploading source maps';
Log.startingTask(taskName);

List<String> params = [];

List<String> _releasesCliParams() {
final params = <String>[];
_setUrlAndTokenAndLog(params);

params.add('releases');

_addOrgAndProject(params);
return params;
}

void _executeNewRelease() {
_executeAndLog('Failed to create a new release',
[..._releasesCliParams(), 'new', _release]);
}

List<String> releaseFinalizeParams = [];
releaseFinalizeParams.addAll(params);
void _executeFinalizeRelease() {
_executeAndLog('Failed to finalize the new release',
[..._releasesCliParams(), 'finalize', _release]);
}

void _executeSetCommits() {
final params = [
..._releasesCliParams(),
'set-commits',
_release,
];

if (['auto', 'true', ''].contains(_configuration.commits.toLowerCase())) {
params.add('--auto');
} else {
params.add('--commit');
params.add(_configuration.commits);
}

// create new release
List<String> releaseNewParams = [];
releaseNewParams.addAll(params);
releaseNewParams.add('new');
_executeAndLog('Failed to set commits', params);
}

final release = _getRelease();
releaseNewParams.add(release);
void _executeCliForSourceMaps() {
const taskName = 'uploading source maps';
Log.startingTask(taskName);

_executeAndLog('Failed to create new release', releaseNewParams);
List<String> params = _releasesCliParams();

// upload source maps (js and map)
List<String> releaseJsFilesParams = [];
releaseJsFilesParams.addAll(params);

_addExtensionToParams(['map', 'js'], releaseJsFilesParams, release,
_addExtensionToParams(['map', 'js'], releaseJsFilesParams, _release,
_configuration.webBuildFilesFolder);

_addWait(releaseJsFilesParams);
Expand All @@ -111,19 +137,13 @@ class SentryDartPlugin {
List<String> releaseDartFilesParams = [];
releaseDartFilesParams.addAll(params);

_addExtensionToParams(['dart'], releaseDartFilesParams, release,
_addExtensionToParams(['dart'], releaseDartFilesParams, _release,
_configuration.buildFilesFolder);

_addWait(releaseDartFilesParams);

_executeAndLog('Failed to upload source maps', releaseDartFilesParams);

// finalize new release
releaseFinalizeParams.add('finalize');
releaseFinalizeParams.add(release);

_executeAndLog('Failed to create new release', releaseFinalizeParams);

Log.taskCompleted(taskName);
}

Expand Down Expand Up @@ -178,9 +198,7 @@ class SentryDartPlugin {
}
}

String _getRelease() {
return '${_configuration.name}@${_configuration.version}';
}
String get _release => '${_configuration.name}@${_configuration.version}';

void _addWait(List<String> params) {
if (_configuration.waitForProcessing) {
Expand Down
7 changes: 4 additions & 3 deletions lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:io';

import 'package:file/file.dart';
import 'package:process/process.dart';
import 'package:sentry_dart_plugin/src/cli/_sources.dart';
import 'package:system_info2/system_info2.dart';
import 'package:yaml/yaml.dart';

Expand Down Expand Up @@ -58,6 +57,8 @@ class Configuration {
/// the Web Build folder, defaults to build/web
late String webBuildFilesFolder;

late String commits;
vaind marked this conversation as resolved.
Show resolved Hide resolved

dynamic _getPubspec() {
final file = injector.get<FileSystem>().file("pubspec.yaml");
if (!file.existsSync()) {
Expand Down Expand Up @@ -87,6 +88,7 @@ class Configuration {
uploadNativeSymbols = config?['upload_native_symbols'] ?? true;
uploadSourceMaps = config?['upload_source_maps'] ?? false;
includeNativeSources = config?['include_native_sources'] ?? false;
commits = (config?['commits'] ?? 'auto').toString();

// uploading JS and Map files need to have the correct folder structure
// otherwise symbolication fails, the default path for the web build folder is build/web
Expand Down Expand Up @@ -147,7 +149,6 @@ class Configuration {
}

Future<void> _findAndSetCliPath() async {
final cliSetup = CLISetup(currentCLISources);
HostPlatform? platform;
if (Platform.isMacOS) {
platform = HostPlatform.darwinUniversal;
Expand Down Expand Up @@ -179,7 +180,7 @@ class Configuration {
}

try {
cliPath = await cliSetup.download(platform);
cliPath = await injector.get<CLISetup>().download(platform);
} on Exception catch (e) {
Log.error("Failed to download Sentry CLI: $e");
return _setPreInstalledCli();
Expand Down
3 changes: 3 additions & 0 deletions lib/src/utils/injector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:file/local.dart';
import 'package:injector/injector.dart';
import 'package:process/process.dart';

import '../cli/_sources.dart';
import '../cli/setup.dart';
import '../configuration.dart';

/// Injector singleton instance
Expand All @@ -13,4 +15,5 @@ void initInjector() {
injector.registerSingleton<Configuration>(() => Configuration());
injector.registerSingleton<ProcessManager>(() => LocalProcessManager());
injector.registerSingleton<FileSystem>(() => LocalFileSystem());
injector.registerSingleton<CLISetup>(() => CLISetup(currentCLISources));
}
Loading