Проблема

Занимаясь разработкой пакета, который используется клиентами на разных версиях продукта и php, столкнулся с проблемой лимитов GitHub API при функциональном тестировании.

Для тестирования изменений в пул-реквестах используется Jenkins с большой матрицей тестов. Количество тестов, разные версии продукта, плюс разные версии php - получаем действительно большую матрицу функциональных тестов.

После очередного добавления новой версии продукта в матрицу тесты начали падать случайным образом с ошибкой “Could not authenticate against github.com”.

Почему так происходит?

Все дело в том, что я тяну тестируемый пакет и пару его зависимостей с GitHub.

Jenkins на одном из этапов генерирует такой composer.json, подставляя нужную версию продукта и php:

{
  "name": "vendor/tested-package",
  "description": "The example",
  "version": "1.2.3",
  "repositories": {
    "tested-package-repo": {
      "type": "vcs",
      "url": "https://github.com/vendor/tested-package.git"
    },
    "dependency-1": {
      "type": "vcs",
      "url": "https://github.com/vendor/dependency-1.git"
    },
    "dependency-2": {
      "type": "vcs",
      "url": "https://github.com/vendor/dependency-2.git"
    },
    "dependency-3": {
      "type": "vcs",
      "url": "https://github.com/vendor/dependency-3.git"
    },
    "composer-repo.vendor.org": {
      "type": "composer",
      "url": "composer-repo.vendor.org"
    }
  },
  "require": {
    "php": "7.2",
    "vendor/product": "1.10.22",
    "vendor/tested-package": "dev-develop as 1.2.3",
    "vendor/dependency-1": "dev-develop as 2.0.1",
    "vendor/dependency-2": "dev-develop as 2.0.2",
    "vendor/dependency-3": "dev-develop as 2.0.3"
  }
}

На следующем этапе Jenkins выполняет composer update, а затем запускает функциональный тест.

Composer для получения информации с репозиториев использует GitHub API. Мой лимит 5000 запросов к API в час. Но учитывая количество тестов, версий продукта и php - я начал вылезать за рамки этого лимита.

Как проверить GitHub API лимиты?

Чтобы проверить сколько у тебя осталось запросов к GitHub API можно выполнить следующую команду в терминале:

curl -H "Accept: application/vnd.github+json" -H "Authorization: token <your-token>" https://api.github.com/rate_limit

<your-token> нужно заменить на твой GitHub токен, который ты используешь в auth.json

Пример ответа от GitHub:

{
  "resources": {
    "core": {
      "limit": 5000,
      "used": 5001,
      "remaining": 0,
      "reset": 1662134609
    },
    "search": {
      "limit": 30,
      "used": 0,
      "remaining": 30,
      "reset": 1662133328
    },
    "graphql": {
      "limit": 5000,
      "used": 0,
      "remaining": 5000,
      "reset": 1662136868
    },
    "integration_manifest": {
      "limit": 5000,
      "used": 0,
      "remaining": 5000,
      "reset": 1662136868
    },
    "source_import": {
      "limit": 100,
      "used": 0,
      "remaining": 100,
      "reset": 1662133328
    },
    "code_scanning_upload": {
      "limit": 1000,
      "used": 0,
      "remaining": 1000,
      "reset": 1662136868
    },
    "actions_runner_registration": {
      "limit": 10000,
      "used": 0,
      "remaining": 10000,
      "reset": 1662136868
    },
    "scim": {
      "limit": 15000,
      "used": 0,
      "remaining": 15000,
      "reset": 1662136868
    },
    "dependency_snapshots": {
      "limit": 100,
      "used": 0,
      "remaining": 100,
      "reset": 1662133328
    }
  },
  "rate": {
    "limit": 5000,
    "used": 5001,
    "remaining": 0,
    "reset": 1662134609
  }
}

Нас интересует вот эта часть:

...
"rate": {
    "limit": 5000,
    "used": 5001,
    "remaining": 0,
    "reset": 1662134609
}
...
  • limit - сколько всего дается запросов в час
  • used - сколько использовано запросов
  • remaining - сколько осталось запросов
  • reset - время когда произойдет сброс

В этом примере можно увидеть, что лимит исчерпан.

Решение проблемы с GitHub API

Использование no-api

Один из вариантов - добавление опции no-api к каждому GitHub репозиторию в composer.json:

{
  "name": "vendor/tested-package",
  "description": "The example",
  "version": "1.2.3",
  "repositories": {
    "tested-package-repo": {
      "type": "vcs",
      "url": "https://github.com/vendor/tested-package.git",
      "no-api": true
    },
    "dependency-1": {
      "type": "vcs",
      "url": "https://github.com/vendor/dependency-1.git",
      "no-api": true
    },
    "dependency-2": {
      "type": "vcs",
      "url": "https://github.com/vendor/dependency-2.git",
      "no-api": true
    },
    "dependency-3": {
      "type": "vcs",
      "url": "https://github.com/vendor/dependency-3.git",
      "no-api": true
    },
    "composer-repo.vendor.org": {
      "type": "composer",
      "url": "composer-repo.vendor.org"
    }
  },
  "require": {
    "php": "7.2",
    "vendor/product": "1.10.22",
    "vendor/tested-package": "dev-develop as 1.2.3",
    "vendor/dependency-1": "dev-develop as 2.0.1",
    "vendor/dependency-2": "dev-develop as 2.0.2",
    "vendor/dependency-3": "dev-develop as 2.0.3"
  }
}

Используя эту опцию composer не использует GitHub API для получения информации с репозитория, вместо этого он клонирует репозиторий и считавает нужную иформацию с локально репозитория.

Этот подход решает проблему с API лимитами, но выполнение тестов может занять в 2-3 раза больше времени, так как клонирование более медленное чем использование API.

Использование собственного composer репозитория

Можно поднять на машине с Jenkins свой composer репозиторий и использовать его для хранения тестируемых пакетов и их зависимостей.

Что я и сделал.

Перед этапом генерации composer.json я добавил еще один этап - упаковка и публикация тестируемого пакета и его зависимостей в свой composer репозиторий.

После этих изменений composer.json имеет следующий вид:

{
  "name": "vendor/tested-package",
  "description": "The example",
  "version": "1.2.3",
  "repositories": {
    "test-repo": {
      "type": "composer",
      "url": "127.0.0.1"
    }
    "composer-repo.vendor.org": {
      "type": "composer",
      "url": "composer-repo.vendor.org"
    }
  },
  "require": {
    "php": "7.2",
    "vendor/product": "1.10.22",
    "vendor/tested-package": "1.2.3",
    "vendor/dependency-1": "2.0.1",
    "vendor/dependency-2": "2.0.2",
    "vendor/dependency-3": "2.0.3"
  }
}

Проблема с GitHub API лимитами была решена без ущерба скорости выполнения тестов.

Для поднятия собственного composer репозитория ты можешь использовать, например, Satis

Если есть ещё какие-то идеи решения этой проблемы - пиши в комментариях.