"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getLatestAvailableVersion = exports.getAvailableVersions = void 0;
var _promises = require("fs/promises");
var _path = _interopRequireDefault(require("path"));
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
var _pRetry = _interopRequireDefault(require("p-retry"));
var _lodash = require("lodash");
var _gte = _interopRequireDefault(require("semver/functions/gte"));
var _gt = _interopRequireDefault(require("semver/functions/gt"));
var _coerce = _interopRequireDefault(require("semver/functions/coerce"));
var _repoInfo = require("@kbn/repo-info");
var _ = require("..");
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */

const MINIMUM_SUPPORTED_VERSION = '7.17.0';
const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_list.json';

// Endpoint maintained by the web-team and hosted on the elastic website
const PRODUCT_VERSIONS_URL = 'https://www.elastic.co/api/product_versions';
const MAX_REQUEST_TIMEOUT = 60 * 1000; // Only attempt to fetch product versions for one minute total

// Cache available versions in memory for 1 hour
const CACHE_DURATION = 1000 * 60 * 60;
let CACHED_AVAILABLE_VERSIONS;
let LAST_FETCHED;
const getLatestAvailableVersion = async includeCurrentVersion => {
  const versions = await getAvailableVersions({
    includeCurrentVersion
  });
  return versions[0];
};
exports.getLatestAvailableVersion = getLatestAvailableVersion;
const getAvailableVersions = async ({
  includeCurrentVersion,
  ignoreCache = false // This is only here to allow us to ignore the cache in tests
} = {}) => {
  var _config$internal;
  const logger = _.appContextService.getLogger();
  if (LAST_FETCHED && !ignoreCache) {
    const msSinceLastFetched = Date.now() - (LAST_FETCHED || 0);
    if (msSinceLastFetched < CACHE_DURATION && CACHED_AVAILABLE_VERSIONS !== undefined) {
      logger.debug(`Cache is valid, returning cached available versions`);
      return CACHED_AVAILABLE_VERSIONS;
    }
    logger.debug('Cache has expired, fetching available versions from disk + API');
  }
  const config = _.appContextService.getConfig();
  const kibanaVersion = _.appContextService.getKibanaVersion();
  let availableVersions = [];

  // First, grab available versions from the static file that's placed on disk at build time
  try {
    const file = await (0, _promises.readFile)(_path.default.join(_repoInfo.REPO_ROOT, AGENT_VERSION_BUILD_FILE), 'utf-8');
    const data = JSON.parse(file);
    availableVersions = [...availableVersions, ...data];
  } catch (error) {
    // If we can't read from the file, the error is non-blocking. We'll try to source data from the
    // product versions API later.
    logger.debug(`Error reading file ${AGENT_VERSION_BUILD_FILE}: ${error.message}`);
  }

  // Next, fetch from the product versions API. This API call is aggressively cached, so we won't
  // fetch from the live API more than `TIME_BETWEEN_FETCHES` milliseconds.
  const apiVersions = await fetchAgentVersionsFromApi();

  // Coerce each version to a semver object and compare to our `MINIMUM_SUPPORTED_VERSION` - we
  // only want support versions in the final result. We'll also sort by newest version first.
  availableVersions = (0, _lodash.uniq)([...availableVersions, ...apiVersions]).map(item => {
    var _semverCoerce;
    return ((_semverCoerce = (0, _coerce.default)(item)) === null || _semverCoerce === void 0 ? void 0 : _semverCoerce.version) || '';
  }).filter(v => (0, _gte.default)(v, MINIMUM_SUPPORTED_VERSION)).sort((a, b) => (0, _gt.default)(a, b) ? -1 : 1);

  // If the current stack version isn't included in the list of available versions, add it
  // at the front of the array
  const hasCurrentVersion = availableVersions.some(v => v === kibanaVersion);
  if (includeCurrentVersion && !hasCurrentVersion) {
    availableVersions = [kibanaVersion, ...availableVersions];
  }

  // Allow upgrading to the current stack version if this override flag is provided via `kibana.yml`.
  // This is useful for development purposes.
  if (availableVersions.length === 0 && !(config !== null && config !== void 0 && (_config$internal = config.internal) !== null && _config$internal !== void 0 && _config$internal.onlyAllowAgentUpgradeToKnownVersions)) {
    availableVersions = [kibanaVersion];
  }

  // Don't prime the cache in tests
  if (!ignoreCache) {
    CACHED_AVAILABLE_VERSIONS = availableVersions;
    LAST_FETCHED = Date.now();
  }
  return availableVersions;
};
exports.getAvailableVersions = getAvailableVersions;
async function fetchAgentVersionsFromApi() {
  const logger = _.appContextService.getLogger();
  const options = {
    headers: {
      'Content-Type': 'application/json'
    }
  };
  try {
    const response = await (0, _pRetry.default)(() => (0, _nodeFetch.default)(PRODUCT_VERSIONS_URL, options), {
      retries: 1,
      maxRetryTime: MAX_REQUEST_TIMEOUT
    });
    const rawBody = await response.text();

    // We need to handle non-200 responses gracefully here to support airgapped environments where
    // Kibana doesn't have internet access to query this API
    if (response.status >= 400) {
      logger.debug(`Status code ${response.status} received from versions API: ${rawBody}`);
      return [];
    }
    const jsonBody = JSON.parse(rawBody);
    const versions = (jsonBody.length ? jsonBody[0] : []).filter(item => {
      var _item$title;
      return item === null || item === void 0 ? void 0 : (_item$title = item.title) === null || _item$title === void 0 ? void 0 : _item$title.includes('Elastic Agent');
    }).map(item => item === null || item === void 0 ? void 0 : item.version_number);
    return versions;
  } catch (error) {
    logger.debug(`Error fetching available versions from API: ${error.message}`);
    return [];
  }
}