1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:meta/meta.dart';
8
9import '../persistent_tool_state.dart';
10import 'io.dart';
11import 'net.dart';
12import 'platform.dart';
13
14class BotDetector {
15 BotDetector({
16 required HttpClientFactory httpClientFactory,
17 required Platform platform,
18 required PersistentToolState persistentToolState,
19 }) : _platform = platform,
20 _azureDetector = AzureDetector(httpClientFactory: httpClientFactory),
21 _persistentToolState = persistentToolState;
22
23 final Platform _platform;
24 final AzureDetector _azureDetector;
25 final PersistentToolState _persistentToolState;
26
27 Future<bool> get isRunningOnBot async {
28 if ( // Explicitly stated to not be a bot.
29 _platform.environment['BOT'] == 'false'
30 // Set by the IDEs to the IDE name, so a strong signal that this is not a bot.
31 ||
32 _platform.environment.containsKey('FLUTTER_HOST')
33 // When set, GA logs to a local file (normally for tests) so we don't need to filter.
34 ||
35 _platform.environment.containsKey('FLUTTER_ANALYTICS_LOG_FILE')) {
36 _persistentToolState.setIsRunningOnBot(false);
37 return false;
38 }
39
40 if (_persistentToolState.isRunningOnBot != null) {
41 return _persistentToolState.isRunningOnBot!;
42 }
43
44 final bool result =
45 _platform.environment['BOT'] == 'true'
46 // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
47 ||
48 _platform.environment['TRAVIS'] == 'true' ||
49 _platform.environment['CONTINUOUS_INTEGRATION'] == 'true' ||
50 _platform.environment.containsKey('CI') // Travis and AppVeyor
51 // https://www.appveyor.com/docs/environment-variables/
52 ||
53 _platform.environment.containsKey('APPVEYOR')
54 // https://cirrus-ci.org/guide/writing-tasks/#environment-variables
55 ||
56 _platform.environment.containsKey('CIRRUS_CI')
57 // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
58 ||
59 (_platform.environment.containsKey('AWS_REGION') &&
60 _platform.environment.containsKey('CODEBUILD_INITIATOR'))
61 // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables
62 ||
63 _platform.environment.containsKey('JENKINS_URL')
64 // https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
65 ||
66 _platform.environment.containsKey('GITHUB_ACTIONS')
67 // Properties on Flutter's Chrome Infra bots.
68 ||
69 _platform.environment['CHROME_HEADLESS'] == '1' ||
70 _platform.environment.containsKey('BUILDBOT_BUILDERNAME') ||
71 _platform.environment.containsKey('SWARMING_TASK_ID')
72 // Property when running on borg.
73 ||
74 _platform.environment.containsKey('BORG_ALLOC_DIR')
75 // Property when running on Azure.
76 ||
77 await _azureDetector.isRunningOnAzure;
78
79 _persistentToolState.setIsRunningOnBot(result);
80 return result;
81 }
82}
83
84// Are we running on Azure?
85// https://docs.microsoft.com/en-us/azure/virtual-machines/linux/instance-metadata-service
86@visibleForTesting
87class AzureDetector {
88 AzureDetector({required HttpClientFactory httpClientFactory})
89 : _httpClientFactory = httpClientFactory;
90
91 static const _serviceUrl = 'http://169.254.169.254/metadata/instance';
92
93 final HttpClientFactory _httpClientFactory;
94
95 bool? _isRunningOnAzure;
96
97 Future<bool> get isRunningOnAzure async {
98 if (_isRunningOnAzure != null) {
99 return _isRunningOnAzure!;
100 }
101 const connectionTimeout = Duration(milliseconds: 250);
102 const requestTimeout = Duration(seconds: 1);
103 final HttpClient client = _httpClientFactory()..connectionTimeout = connectionTimeout;
104 try {
105 final HttpClientRequest request = await client
106 .getUrl(Uri.parse(_serviceUrl))
107 .timeout(requestTimeout);
108 request.headers.add('Metadata', true);
109 await request.close();
110 } on SocketException {
111 // If there is an error on the socket, it probably means that we are not
112 // running on Azure.
113 return _isRunningOnAzure = false;
114 } on HttpException {
115 // If the connection gets set up, but encounters an error condition, it
116 // still means we're on Azure.
117 return _isRunningOnAzure = true;
118 } on TimeoutException {
119 // The HttpClient connected to a host, but it did not respond in a timely
120 // fashion. Assume we are not on a bot.
121 return _isRunningOnAzure = false;
122 } on OSError {
123 // The HttpClient might be running in a WSL1 environment.
124 return _isRunningOnAzure = false;
125 }
126 // We got a response. We're running on Azure.
127 return _isRunningOnAzure = true;
128 }
129}
130