describeTrails([ 'trailNameList' => [$trailName] ]); $s3BucketName = $result->search('trailList[0].S3BucketName'); $options[self::KEY_PREFIX] = $result->search( 'trailList[0].S3KeyPrefix' ); } catch (CloudTrailException $e) { // There was an error describing the trail } // If the bucket name is still unknown, then throw an exception if (!$s3BucketName) { $prev = isset($e) ? $e : null; throw new \InvalidArgumentException('The bucket name could not ' . 'be determined from the trail.', 0, $prev); } return new self($s3Client, $s3BucketName, $options); } /** * Constructs a LogFileIterator using the specified options: * * - trail_name: The name of the trail that is generating our logs. If * none is provided, then "Default" will be used, since that is the name * of the trail created in the AWS Management Console. * - key_prefix: The S3 key prefix of your log files. This value will be * overwritten when using the `fromTrail()` method. However, if you are * using the constructor, then this value will be used. * - start_date: The timestamp of the beginning of date range of the log * records you want to read. You can pass this in as a `DateTime` object, * integer (unix timestamp), or a string compatible with `strtotime()`. * - end_date: The timestamp of the end of date range of the log records * you want to read. You can pass this in as a `DateTime` object, integer * (unix timestamp), or a string compatible with `strtotime()`. * - account_id: This is your AWS account ID, which is the 12-digit number * found on the *Account Identifiers* section of the *AWS Security * Credentials* page. See https://console.aws.amazon.com/iam/home?#security_credential * - log_region: Region of the services of the log records you want to read. * * @param S3Client $s3Client * @param string $s3BucketName * @param array $options */ public function __construct( S3Client $s3Client, $s3BucketName, array $options = [] ) { $this->s3Client = $s3Client; $this->s3BucketName = $s3BucketName; parent::__construct($this->buildListObjectsIterator($options)); } /** * An override of the typical current behavior of \IteratorIterator to * format the output such that the bucket and key are returned in an array * * @return array|bool */ #[\ReturnTypeWillChange] public function current() { if ($object = parent::current()) { return [ 'Bucket' => $this->s3BucketName, 'Key' => $object['Key'] ]; } return false; } /** * Constructs an S3 ListObjects iterator, optionally decorated with * FilterIterators, based on the provided options. * * @param array $options * * @return \Iterator */ private function buildListObjectsIterator(array $options) { // Extract and normalize the date values from the options $startDate = isset($options[self::START_DATE]) ? $this->normalizeDateValue($options[self::START_DATE]) : null; $endDate = isset($options[self::END_DATE]) ? $this->normalizeDateValue($options[self::END_DATE]) : null; // Determine the parts of the key prefix of the log files being read $parts = [ 'prefix' => isset($options[self::KEY_PREFIX]) ? $options[self::KEY_PREFIX] : null, 'account' => isset($options[self::ACCOUNT_ID]) ? $options[self::ACCOUNT_ID] : self::PREFIX_WILDCARD, 'region' => isset($options[self::LOG_REGION]) ? $options[self::LOG_REGION] : self::PREFIX_WILDCARD, 'date' => $this->determineDateForPrefix($startDate, $endDate), ]; // Determine the longest key prefix that can be used to retrieve all // of the relevant log files. $candidatePrefix = ltrim(strtr(self::PREFIX_TEMPLATE, $parts), '/'); $logKeyPrefix = $candidatePrefix; $index = strpos($candidatePrefix, self::PREFIX_WILDCARD); if ($index !== false) { $logKeyPrefix = substr($candidatePrefix, 0, $index); } // Create an iterator that will emit all of the objects matching the // key prefix. $objectsIterator = $this->s3Client->getIterator('ListObjects', [ 'Bucket' => $this->s3BucketName, 'Prefix' => $logKeyPrefix, ]); // Apply regex and/or date filters to the objects iterator to emit only // log files matching the options. $objectsIterator = $this->applyRegexFilter( $objectsIterator, $logKeyPrefix, $candidatePrefix ); $objectsIterator = $this->applyDateFilter( $objectsIterator, $startDate, $endDate ); return $objectsIterator; } /** * Normalizes a date value to a unix timestamp * * @param int|string|\DateTimeInterface $date * * @return int * @throws \InvalidArgumentException if the value cannot be converted to * a timestamp */ private function normalizeDateValue($date) { if (is_string($date)) { $date = strtotime($date); } elseif ($date instanceof \DateTimeInterface) { $date = $date->format('U'); } elseif (!is_int($date)) { throw new \InvalidArgumentException('Date values must be a ' . 'string, an int, or a DateTime object.'); } return $date; } /** * Uses the provided date values to determine the date portion of the prefix */ private function determineDateForPrefix($startDate, $endDate) { // The default date value should look like "*/*/*" after joining $dateParts = array_fill_keys(['Y', 'm', 'd'], self::PREFIX_WILDCARD); // Narrow down the date by replacing the WILDCARDs with values if they // are the same for the start and end date. if ($startDate && $endDate) { foreach ($dateParts as $key => &$value) { $candidateValue = date($key, $startDate); if ($candidateValue === date($key, $endDate)) { $value = $candidateValue; } else { break; } } } return join('/', $dateParts); } /** * Applies a regex iterator filter that limits the ListObjects result set * based on the provided options. * * @param \Iterator $objectsIterator * @param string $logKeyPrefix * @param string $candidatePrefix * * @return \Iterator */ private function applyRegexFilter( $objectsIterator, $logKeyPrefix, $candidatePrefix ) { // If the prefix and candidate prefix are not the same, then there were // WILDCARDs. if ($logKeyPrefix !== $candidatePrefix) { // Turn the candidate prefix into a regex by trimming and // converting WILDCARDs to regex notation. $regex = rtrim($candidatePrefix, '/' . self::PREFIX_WILDCARD) . '/'; $regex = strtr($regex, [self::PREFIX_WILDCARD => '[^/]+']); // After trimming WILDCARDs or the end, if the regex is the same as // the prefix, then no regex is needed. if ($logKeyPrefix !== $regex) { // Apply a regex filter iterator to remove files that don't // match the provided options. $objectsIterator = new \CallbackFilterIterator( $objectsIterator, function ($object) use ($regex) { return preg_match("#{$regex}#", $object['Key']); } ); } } return $objectsIterator; } /** * Applies an iterator filter to restrict the ListObjects result set to the * specified date range. * * @param \Iterator $objectsIterator * @param int $startDate * @param int $endDate * * @return \Iterator */ private function applyDateFilter($objectsIterator, $startDate, $endDate) { // If either a start or end date was provided, filter out dates that // don't match the date range. if ($startDate || $endDate) { $fn = function ($object) use ($startDate, $endDate) { if (!preg_match('/[0-9]{8}T[0-9]{4}Z/', $object['Key'], $m)) { return false; } $date = strtotime($m[0]); return (!$startDate || $date >= $startDate) && (!$endDate || $date <= $endDate); }; $objectsIterator = new \CallbackFilterIterator($objectsIterator, $fn); } return $objectsIterator; } }