diff --git a/CHANGELOG_API.md b/CHANGELOG_API.md new file mode 100644 index 000000000000..420e68918691 --- /dev/null +++ b/CHANGELOG_API.md @@ -0,0 +1,56 @@ +## API v1.0.0 + +### Summary + +API v1 is a major rework of the API v0 with a lot of breaking changes. Compared to the API v0, API v1: + +- feels more "Restful", +- is versioned using the `api/v1` prefix, making it easier to implement non-backward-compatible + changes, +- is generated using a Jekyll Generator (see https://jekyllrb.com/docs/plugins/generators/). + +API v1 resolves : #394, #759, #905, #2062, #2066, #2078, #2160, #2331, #2431, #2595. It also reverts +#2425 due to incompatibilities in redirect rules. + +The API v0 is still generated to give time to users to migrate to API v1. + +### Changes in the "All products" endpoint + +- Path has been changed from `api/all.json` to `api/v1/products/` +- Response has been changed from a simple array to a JSON document. This made it possible to add endoflife-level data, such as the number of products. +- Array elements have been changed from a simple string to a full JSON document. This made it possible to expose new data, such as product category and tags (#2062). + +### Changes in the "Product" endpoint + +- Path has been changed from `api/.json` to `api/v1/products//`. +- Response has been changed from a simple array to a JSON document. This made it possible to expose product-level data, such as product category and tags (#2062). +- Cycles data now always contain all the release cycles properties, even if they are null (example: `discontinued`, `latest`, `latestReleaseDate`, `support`...). + +### Changes in the "Cycle" endpoint + +- Path has been changed from `api//.json` to `api/v1/products//cycles//`. +- Cycles data now always contain all the release cycles properties, even if they are null (example: `discontinued`, `latest`, `latestReleaseDate`, `support`...). +- A special `/api/v1/products//cycles/latest/` cycle, containing the same data as the latest cycle, has been added (#2078). + +### Changes in 404 error responses + +404 error JSON responses are not returned anymore. #2425 has been reverted because it conflicted +with the rule that rewrites the paths to add `/index.json` to all requests, which is also a global +rule and [takes precedence](https://docs.netlify.com/routing/redirects/#rule-processing-order). + +### New endpoints + +- `/api/v1/categories/` - list categories used on endoflife.date +- `/api/v1/categories/` - list products having the given category +- `/api/v1/tags/` - list tags used on endoflife.date +- `/api/v1/tags/` - list products having the given tag + + + +## API v0 + +On 2023-03-02 the v0 endpoints were: + +- "All products" (`/api/all.json`) : Return a list of all products. +- "Product" (`/api/{product}.json`) : Get EoL dates of all cycles for a given product. +- "Cycle" (`/api/{product}/{cycle}.json`) : Details of a single release cycle of a given product. diff --git a/HACKING.md b/HACKING.md index fe54fdc52c51..deae201869d4 100644 --- a/HACKING.md +++ b/HACKING.md @@ -105,7 +105,13 @@ The API is just JSON files generated in the `api` directory by `_plugins/create- ### API Documentation -The API Documentation is available at and is generated from an OpenAPI Specification file located at `_data/openapi.yml`. The documentation is rendered [Stoplight Elements](https://meta.stoplight.io/docs/elements/ZG9jOjMyNjU4OTY0-introduction-to-elements). +The current API v1 documentation is available at and is +generated from an OpenAPI Specification file located at `api_v1/openapi.yml`. The documentation is +rendered by [Swagger UI](https://swagger.io/tools/swagger-ui/). + +The old API v0 documentation is available at and is +generated from an OpenAPI Specification file located at `assets/openapi.yml`. The documentation is +rendered by [Stoplight Elements](https://meta.stoplight.io/docs/elements/ZG9jOjMyNjU4OTY0-introduction-to-elements). ## Contributing Workflow diff --git a/README.md b/README.md index fbff44bd7f2b..455ae74a3220 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,8 @@ While participating in the project, you must abide by its [Code of Conduct](CODE ## API -An API is available for integration with CI platforms. -API documentation is available at https://endoflife.date/docs/api. -The API is currently in Alpha, and breaking changes can happen. +An API is available for integration with CI platforms. API documentation is available at https://endoflife.date/docs/api/v1/. +The API is currently in Beta, and breaking changes can happen. ## License @@ -46,6 +45,8 @@ endoflife.date is relying on various amazing software and components : - [Just the Docs](https://github.com/just-the-docs/just-the-docs), a documentation theme for Jekyll. - [Stoplight Elements](https://stoplight.io/open-source/elements), a collection of UI components for displaying beautiful developer documentation from any OpenAPI document. +- [Swagger UI](https://swagger.io/tools/swagger-ui/), a documentation generator for OpenAPI + Specification. - [Simple Icons](https://simpleicons.org/), free SVG icons for popular brands. - Our icon is derived from [Hourglass icon (orange)](https://commons.wikimedia.org/wiki/File:Hourglass_icon_%28orange%29.svg) by David Abián and Serhio Magpie on the English Wikipedia. Remixed under the CC-BY-SA-4.0 license. diff --git a/_config.yml b/_config.yml index 3fee2a5da028..1e0b428ad7ac 100644 --- a/_config.yml +++ b/_config.yml @@ -40,7 +40,7 @@ aux_links: source: - https://github.com/endoflife-date/endoflife.date api: - - /docs/api + - /docs/api/v1/ jekyll_timeago: # Use 2 terms in relative timestamps: # [YES] x years, y months diff --git a/_headers b/_headers index 24cb2b006d9c..c1460af8cf63 100644 --- a/_headers +++ b/_headers @@ -56,6 +56,10 @@ layout: null Content-Security-Policy: default-src 'none'; manifest-src 'self'; connect-src 'self'; script-src 'self'; style-src 'self'; img-src {{ defaultCspImgSrc }} {{ releaseImageSrc }} Link: /api{{page.permalink}}.json; rel=alternate;type=application/json Link: /calendar{{page.permalink}}.ics; rel=alternate;type=text/calendar + {% elsif page.permalink contains '/docs/api/v' %} + {%- comment %}Used contains to match all API version (startswith does not exist){% endcomment %} + # unsafe-inline and data: should not be an issue for a static site + Content-Security-Policy: default-src 'none'; manifest-src 'self'; connect-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com/; style-src 'self' https://unpkg.com/; img-src 'self' data: {% elsif page.permalink == '/docs/api' %} Content-Security-Policy: default-src 'none'; manifest-src 'self'; connect-src 'self'; script-src 'self' https://unpkg.com/@stoplight/elements/web-components.min.js; style-src 'self' https://unpkg.com/@stoplight/elements/ {% else %} diff --git a/_layouts/json.json b/_layouts/json.json new file mode 100644 index 000000000000..8c98299c885e --- /dev/null +++ b/_layouts/json.json @@ -0,0 +1 @@ +{{ page.data | jsonify }} diff --git a/_layouts/product.html b/_layouts/product.html index 1faccfa0a7bc..3d65184cba7b 100644 --- a/_layouts/product.html +++ b/_layouts/product.html @@ -170,8 +170,8 @@

- A JSON version of this page is available at /api{{page.permalink}}.json. - See the API Documentation for more information. + A JSON version of this page is available at /api/v1/products{{page.permalink}}/. + See the API Documentation for more information. You can subscribe to the iCalendar feed at /calendar{{page.permalink}}.ics.

diff --git a/_layouts/swagger-ui.html b/_layouts/swagger-ui.html new file mode 100644 index 000000000000..b75f0d38846e --- /dev/null +++ b/_layouts/swagger-ui.html @@ -0,0 +1,31 @@ +--- +layout: null +--- + + + + + {{ page.title }} + + + + + +
+ + + + + + diff --git a/_plugins/generate-api-v1.rb b/_plugins/generate-api-v1.rb index 2f93923af10f..3640e878f20b 100755 --- a/_plugins/generate-api-v1.rb +++ b/_plugins/generate-api-v1.rb @@ -1,89 +1,260 @@ -#!/usr/bin/env ruby - # This script creates API files for version 1 of the endoflife.date API. # -# There are three kind of generated files : -# - all.json: contains the list of all the product names. -# - .json: contains a given product data ()including releases data). -# - /.json: contains a given product release data. +# There are multiples endpoints : +# +# - /api/v1/ - list all major endpoints (those not requiring a parameter) +# - /api/v1/products/ - list all products +# - /api/v1/products// - get a single product details +# - /api/v1/products//latest - get details on the latest cycle for the given product +# - /api/v1/products// - get details on the given cycle for the given product +# - /api/v1/categories/ - list categories used on endoflife.date +# - /api/v1/categories/ - list products having the given category +# - /api/v1/tags/ - list tags used on endoflife.date +# - /api/v1/tags/ - list products having the given tag + -require 'fileutils' -require 'json' -require 'yaml' -require 'date' +require 'jekyll' module ApiV1 - # This API path - DIR = 'api/v1' + VERSION = '1.0.0' + MAJOR_VERSION = VERSION.split('.')[0] - # Returns the path of a file inside the API namespace. - def self.file(name, *args) - File.join(DIR, name, *args) + STRIP_HTML_BLOCKS = Regexp.union( + //m, + //m, + //m + ) + STRIP_HTML_TAGS = /<.*?>/m + + # Remove HTML from a string (such as an LTS label). + # This is the equivalent of Liquid::StandardFilters.strip_html, which cannot be used + # unfortunately. + def self.strip_html(input) + empty = ''.freeze + result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty) + result.gsub!(STRIP_HTML_TAGS, empty) + result end - # Holds information about a product. - Product = Class.new do - attr_accessor :data - - # Initializes the product with the given product's markdown file. - # The markdown file is expected to contain a YAML front-matter with the appropriate properties. - # - # Copying the data makes it easier to process it. - def initialize(data) - @data = Hash.new - # The product name is derived from the product's permalink (ex. /debian => debian). - @data["name"] = data['permalink'][1..data['permalink'].length] - @data["title"] = data['title'] - @data["category"] = data['category'] - @data["iconSlug"] = data['iconSlug'] - @data["permalink"] = data['permalink'] - @data["versionCommand"] = data['versionCommand'] - @data["auto"] = data.has_key? 'auto' - @data["releasePolicyLink"] = data['releasePolicyLink'] - @data["releases"] = data['releases'].map do |release| - release_data = Hash.new - release_data["name"] = release['releaseCycle'] - release_data["codename"] = release['codename'] - release_data["releaseDate"] = release['releaseDate'] - release_data["support"] = release['support'] - release_data["eol"] = release['eol'] - release_data["discontinued"] = release['discontinued'] - release_data["lts"] = release['lts'] || false # lts is optional, make sure it always has a value - release_data["latest"] = release['latest'] - release_data["latestReleaseDate"] = release['latestReleaseDate'] - release_data + def self.site_url(site, path) + "#{site.config['url']}#{path}" + end + + def self.api_url(site, path) + site_url(site, "/api/v#{ApiV1::MAJOR_VERSION}#{path}") + end + + class ApiGenerator < Jekyll::Generator + safe true + priority :lowest + + TOPIC = "API " + ApiV1::VERSION + ":" + + def generate(site) + @site = site + start = Time.now + Jekyll.logger.info TOPIC, "Generating..." + + product_pages = site.pages.select { |page| page.data['layout'] == 'product' } + add_index_page(site) + add_products_related_pages(site, product_pages) + add_categories_related_pages(site, product_pages) + add_tags_related_pages(site, product_pages) + + Jekyll.logger.info TOPIC, "Done in #{(Time.now - start).round(3)} seconds." + end + + private + + def add_index_page(site) + site.pages << JsonPage.of_raw_data(site, '/', [ + { name: "products", uri: "#{ApiV1.api_url(site, '/products/')}" }, + { name: "categories", uri: "#{ApiV1.api_url(site, '/categories/')}" }, + { name: "tags", uri: "#{ApiV1.api_url(site, '/tags/')}" }, + ]) + end + + def add_products_related_pages(site, products) + add_all_products_page(site, products) + + products.each do |page| + add_product_page(site, page) + add_latest_cycle_page(site, page) + page.data['releases'].each { |cycle| add_cycle_page(site, page, cycle) } end end - def name - data["name"] + def add_all_products_page(site, products) + site.pages << JsonPage.of_products(site, '/products/', products) + end + + def add_product_page(site, product) + site.pages << JsonPage.of_product(site, product) + end + + def add_latest_cycle_page(site, page) + latest = page.data['releases'][0] + site.pages << JsonPage.of_cycle(site, page, latest, 'latest') + end + + def add_cycle_page(site, page, cycle) + site.pages << JsonPage.of_cycle(site, page, cycle) + end + + def add_categories_related_pages(site, products) + products_by_category = products_by_category(products) + + add_all_categories_page(site, products_by_category.keys) + products_by_category.each do |category, products| + add_category_page(site, category, products) + end + end + + def products_by_category(products) + products_by_category = {} + products.each { |product| add_to_map(products_by_category, product.data['category'], product) } + products_by_category + end + + def add_category_page(site, category, products) + site.pages << JsonPage.of_products(site, "/categories/#{category}", products) + end + + def add_all_categories_page(site, categories) + data = categories.map { |category| { name: category, uri: "#{ApiV1.api_url(site, "/categories/#{category}/")}" }} + meta = { total: categories.size() } + site.pages << JsonPage.of_raw_data(site, '/categories/', data, meta) + end + + def add_tags_related_pages(site, products) + products_by_tag = products_by_tag(products) + + add_all_tags_page(site, products_by_tag.keys) + products_by_tag.each do |tag, products| + add_tag_page(site, tag, products) + end + end + + def products_by_tag(products) + products_by_tag = {} + products.each do |product| + product.data['tags'].each { |tag| add_to_map(products_by_tag, tag, product) } + end + products_by_tag + end + + def add_tag_page(site, tag, products) + site.pages << JsonPage.of_products(site, "/tags/#{tag}", products) + end + + def add_all_tags_page(site, tags) + data = tags.map { |tag| { name: tag, uri: "#{ApiV1.api_url(site, "/tags/#{tag}/")}" }} + meta = { total: tags.size() } + site.pages << JsonPage.of_raw_data(site, '/tags/', data, meta) + end + + def add_to_map(map, key, page) + if map.has_key? key + map[key] << page + else + map[key] = [page] + end end end -end -product_names = [] -FileUtils.mkdir_p(ApiV1::file('.')) - -Dir['products/*.md'].each do |file| - # Load and prepare data - raw_data = YAML.safe_load(File.open(file), permitted_classes: [Date]) - product = ApiV1::Product.new(raw_data) - product_names.append(product.name) - - # Write /.json - product_file = ApiV1::file("#{product.name}.json") - File.open(product_file, 'w') { |f| f.puts product.data.to_json } - - # Write all //.json - FileUtils.mkdir_p(ApiV1::file(product.name)) - product.data["releases"].each do |release| - # Any / characters in the name are replaced with - to avoid file errors. - release_file = ApiV1::file(product.name, "#{release['name'].to_s.tr('/', '-')}.json") - File.open(release_file, 'w') { |f| f.puts release.to_json } + class JsonPage < Jekyll::Page + class << self + private :new + + def of_raw_data(site, path, data, metadata = {}) + new(site, path, data, metadata) + end + + def of_products(site, path, products) + data = products.map { |product| product_summary_to_json(site, product) } + meta = { total: products.size() } + new(site, path, data, meta) + end + + def of_product(site, product) + path = "/products/#{product.data['id']}" + data = product_to_json(site, product) + meta = { + # https://github.com/gjtorikian/jekyll-last-modified-at/blob/master/lib/jekyll-last-modified-at/determinator.rb + last_modified: product.data['last_modified_at'].last_modified_at_time.iso8601, + auto: product.data.has_key?('auto'), + } + new(site, path, data, meta) + end + + def of_cycle(site, product, cycle, identifier = nil) + name = identifier ? identifier : cycle['id'] + path = "/products/#{product.data['id']}/cycles/#{name}" + data = cycle_to_json(cycle) + new(site, path, data, {}) + end + + def product_to_json(site, product) + additional_details = { + links: { + icon: product.data['iconUrl'], + html: ApiV1.site_url(site, "/#{product.data['id']}"), + releasePolicy: product.data['releasePolicyLink'], + }, + versionCommand: product.data['versionCommand'], + cycles: product.data['releases'].map { |cycle| cycle_to_json(cycle) } + } + + product_summary_to_json(site, product).except(:uri).merge(additional_details) + end + + def product_summary_to_json(site, product) + { + name: product.data['id'], + label: product.data['title'], + category: product.data['category'], + tags: product.data['tags'], + identifiers: product.data['identifiers'].map { |identifier| { + type: identifier.keys.first, + id: identifier.values.first + } }, + uri: ApiV1.api_url(site, "/products/#{product.data['id']}/") + } + end + + def cycle_to_json(cycle) + { + name: cycle['releaseCycle'], + codename: cycle['codename'], + label: ApiV1.strip_html(cycle['label']), + date: cycle['releaseDate'], + support: cycle['support'], + lts: cycle['lts'], + eol: cycle['eol'], + discontinued: cycle['discontinued'], + extendedSupport: cycle['extendedSupport'], + latest: { + name: cycle['latest'], + date: cycle['latestReleaseDate'], + link: cycle['link'], + } + } + end + end + + def initialize(site, path, data, metadata) + @site = site + @base = site.source + @dir = "api/v#{ApiV1::MAJOR_VERSION}#{path}" + @name = "index.json" + @data = {} + @data['layout'] = 'json' + @data['data'] = metadata + @data['data']['result'] = data + @data['data']['schema_version'] = ApiV1::VERSION + + self.process(@name) + end end end - -# Write /all.json -all_products_file = ApiV1::file('all.json') -File.open(all_products_file, 'w') { |f| f.puts product_names.sort.to_json } diff --git a/_plugins/product-data-enricher.rb b/_plugins/product-data-enricher.rb index 30f8a110afae..29ee1722ae8d 100644 --- a/_plugins/product-data-enricher.rb +++ b/_plugins/product-data-enricher.rb @@ -13,6 +13,7 @@ def enrich(page) set_description(page) set_icon_url(page) set_tags(page) + set_identifiers(page) set_overridden_columns_label(page) page.data["releases"].each { |release| enrich_release(page, release) } @@ -61,6 +62,13 @@ def set_tags(page) page.data['tags'] = tags end + # Set identifiers to empty if it's not present. + def set_identifiers(page) + if !page.data['identifiers'] + page.data['identifiers'] = [] + end + end + # Set properly the column presence/label if it was overridden. def set_overridden_columns_label(page) date_column_names = [ diff --git a/_redirects b/_redirects index b011abd794ca..a24919e09d05 100644 --- a/_redirects +++ b/_redirects @@ -9,7 +9,14 @@ # Setting a layout forces Jekyll to render this file layout: null --- -{%- for page in site.pages -%} +# Rewrite for /api/v1/ to keep URLs clean. +# All API responses are located in an index.json and must be accessible without the file name, such as: +# - /api/v1/index.json -> /api/v1/ +# - /api/v1/products/almalinux/index.json -> /api/v1/products/almalinux/ +# This uses shadowing : https://docs.netlify.com/routing/redirects/rewrites-proxies/#shadowing. +/api/v1/* /api/v1/:splat/index.json 200! + +{% for page in site.pages -%} # Redirects for {{page.path}} {%- if page.alternate_urls %} {%- for url in page.alternate_urls %} @@ -28,6 +35,3 @@ layout: null {%- endif %} {% endfor %} - -# Send API 404 responses in JSON -/api/* /assets/404.json 404 diff --git a/api_v1/openapi.yml b/api_v1/openapi.yml new file mode 100644 index 000000000000..994290268afe --- /dev/null +++ b/api_v1/openapi.yml @@ -0,0 +1,454 @@ +--- +# API v1 description. See https://spec.openapis.org/oas/v3.1.0 for specification. +# Edit using https://editor.swagger.io/. + +permalink: /docs/api/v1/openapi.yml +layout: null +--- +openapi: 3.0.3 + +info: + title: endoflife API + version: "1.0.0-b1" + license: + name: MIT License + url: 'https://github.com/endoflife-date/endoflife.date/blob/master/LICENSE' + description: >- + endoflife.date documents EOL dates and support lifecycles for various products. + The endoflife API allows users to discover and query for those products. + + + Some useful links: + + - [The endoflife.date website](https://endoflife.date/) + + - [The endoflife.date repository](https://github.com/endoflife-date/endoflife.date) + + - [The endoflife.date issue tracker](https://github.com/endoflife-date/endoflife.date/issues/) + + - [The source API definition](https://github.com/endoflife-date/endoflife.date/blob/master/assets/openapi.yml) + +# Replace with your preview URL (such as https://deploy-preview-2080--endoflife-date.netlify.app/api/v1). +servers: + - url: {{ site.url }}/api/v1 + +paths: + /: + get: + summary: List the main endoflife.date API endpoints. + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/UriListResponse' + + /products: + get: + summary: > + List all the products referenced on endoflife.date. + Only a subset of each product's data is returned by this endpoint. + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/ProductListResponse' + + /products/{product}/: + get: + summary: > + Get the given product data. + This endpoint is returning all endoflife.date knows about the product, including release + cycles data. + parameters: + - name: product + in: path + description: 'The name of the product.' + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/ProductResponse' + + /products/{product}/cycles/{cycle}: + get: + summary: Get the given product release cycle data. + parameters: + - name: product + in: path + description: 'The name of the product.' + required: true + schema: + type: string + - name: cycle + in: path + description: 'The name of the cycle.' + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/ProductCycleResponse' + + /products/{product}/cycles/latest/: + get: + summary: Get the latest release cycle data for the given product. + parameters: + - name: product + in: path + description: 'The name of the product.' + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/ProductCycleResponse' + + /categories: + get: + summary: List all endoflife.date categories. + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/UriListResponse' + + /categories/{category}: + get: + summary: > + List all the products referenced on endoflife.date for the given category. + Only a subset of each product's data is returned by this endpoint. + parameters: + - name: category + in: path + description: 'The name of the category.' + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/ProductListResponse' + + /tags: + get: + summary: List all endoflife.date tags. + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/UriListResponse' + + /tags/{tag}: + get: + summary: > + List all the products referenced on endoflife.date for the given tag. + Only a subset of each product's data is returned by this endpoint. + parameters: + - name: tag + in: path + description: 'The name of the tag.' + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/ProductListResponse' + +# Responses must be at the end of the list, contain a schema_version property and be suffixed with +# 'Response' to facilitate maintenance and reading. +components: + schemas: + Uri: + type: object + properties: + name: + type: string + description: Name of the URI + example: tags + uri: + type: string + format: uri + description: URI + example: {{ site.url }}/tags/ + + Identifier: + type: object + properties: + name: + type: type + description: Type of the identifier (types as of 2023-03 are repology, purl and cpe) + example: purl + id: + type: string + description: Identifier + example: cpe:/o:almalinux:almalinux + + ProductVersion: + type: object + properties: + name: + type: string + description: Name of the version. + example: "11.6" + date: + type: string + format: date + description: Release date. + example: "2022-12-17" + link: + type: string + format: uri + description: Link to the changelog or release notes. + example: https://www.debian.org/News/2022/2022091002 + + ProductCycle: + type: object + properties: + name: + type: string + description: Name of the product cycle. + example: "11" + codename: + type: string + nullable: true + description: Name of the product cycle. + example: Bullseye + label: + type: string + description: Label of the product cycle. + example: 11 (Bullseye) (Upcoming LTS) + date: + type: string + format: date + description: Date of the first version of the cycle. + example: "2021-08-14" + support: + oneOf: + - type: string + format: date + - type: boolean + nullable: true + description: End of active support date, or true / false to indicate whether or not the product cycle is still supported. + example: "2021-08-14" + lts: + oneOf: + - type: string + format: date + - type: boolean + nullable: true + description: Start date of the LTS phase, or true / false to indicate whether or not the product cycle is LTS. + example: "2021-08-14" + eol: + oneOf: + - type: string + format: date + - type: boolean + description: End of life date, or true / false to indicate whether or not the product cycle is EOL. + example: "2026-08-15" + discontinued: + oneOf: + - type: string + format: date + - type: boolean + nullable: true + description: Discontinued date, or true / false to indicate whether or not the product cycle is discontinued (mainly used for hardware). + example: "2026-08-15" + extendedSupport: + oneOf: + - type: string + format: date + - type: boolean + nullable: true + description: End of extended support date, or true / false to indicate whether or not the product cycle is still in the extended support phase. + example: "2029-08-15" + latest: + type: object + $ref: '#/components/schemas/ProductVersion' + + ProductSummary: + type: object + properties: + name: + type: string + description: Name of the product + example: debian + label: + type: string + description: Label of the product + example: Debian GNU/Linux + category: + type: string + description: Category of the product + example: os + tags: + type: array + description: Tags associated to the product + items: + type: string + identifiers: + type: array + description: Known identifiers (purl, repology, cpe...) associated to the product + items: + $ref: '#/components/schemas/Identifier' + uri: + type: string + format: uri + description: Link to the full product details + example: {{ site.url }}/api/v1/products/debian/ + + ProductDetails: + type: object + properties: + name: + type: string + description: Name of the product + example: debian + label: + type: string + description: Label of the product + example: Debian GNU/Linux + category: + type: string + description: Category of the product + example: os + tags: + type: array + description: Tags associated to the product + items: + type: string + identifiers: + type: array + description: Known identifiers (purl, repology, cpe...) associated to the product + items: + $ref: '#/components/schemas/Identifier' + # Additional properties (compared to ProductSummary) + links: + type: object + description: Product links. + properties: + icon: + type: string + format: uri + nullable: true + description: Link to the product icon (on https://simpleicons.org/). + example: https://simpleicons.org/icons/debian.svg + html: + type: string + format: uri + description: Link to the product page on endoflife.date. + example: https://endoflife.date/debian + releasePolicy: + type: string + format: uri + nullable: true + description: Link to the product release policy. + example: https://wiki.debian.org/DebianReleases + versionCommand: + type: string + description: Command that can be used to check the current product version. + example: cat /etc/os-release + cycles: + type: array + description: Product release cycles. + items: + $ref: '#/components/schemas/ProductCycle' + + UriListResponse: + type: object + properties: + schema_version: + type: string + description: Version of this schema. + example: 1.0.0 + total: + type: integer + format: int32 + description: Number of uri in the list. + example: 1 + result: + type: array + items: + $ref: '#/components/schemas/Uri' + + ProductListResponse: + type: object + properties: + schema_version: + type: string + description: Version of this schema. + example: 1.0.0 + total: + type: integer + format: int32 + description: Number of products in the list. + example: 1 + result: + type: array + items: + $ref: '#/components/schemas/ProductSummary' + + ProductCycleResponse: + type: object + properties: + schema_version: + type: string + description: Version of this schema. + example: 1.0.0 + result: + $ref: '#/components/schemas/ProductCycle' + + ProductResponse: + type: object + properties: + schema_version: + type: string + description: Version of this schema. + example: 1.0.0 + last_modified: + type: string + format: date-time + description: The time this product was last modified. + example: 2023-03-01T14:05:52+01:00 + auto: + type: boolean + description: Whether or not product versions are automatically updated. + example: true + result: + $ref: '#/components/schemas/ProductDetails' diff --git a/api_v1/swagger-ui.md b/api_v1/swagger-ui.md new file mode 100644 index 000000000000..bd2a634e7b01 --- /dev/null +++ b/api_v1/swagger-ui.md @@ -0,0 +1,6 @@ +--- +title: EndOfLife API v1 Swagger UI +permalink: /docs/api/v1/ +openapi_yml: /docs/api/v1/openapi.yml +layout: swagger-ui +--- diff --git a/assets/404.json b/assets/404.json deleted file mode 100644 index 0238a1f9945e..000000000000 --- a/assets/404.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "message": "Product not found" -} \ No newline at end of file diff --git a/humans.txt b/humans.txt index 33ff45aba201..4843c09bdbd4 100644 --- a/humans.txt +++ b/humans.txt @@ -5,5 +5,5 @@ Contributors: https://github.com/endoflife-date/endoflife.date/graphs/contributo /* SITE */ Software: Jekyll, Netlify, GitHub, Ruby, GitHub Actions -Components: Just the Docs Jekyll Theme, Stoplight Elements, Simple Icons +Components: Just the Docs Jekyll Theme, Stoplight Elements, Swagger UI, Simple Icons Logo: adaptation of "An hourglass in a round icon" by David Abián and Serhio Magpie (https://commons.wikimedia.org/wiki/File:Hourglass_icon_%28orange%29.svg) diff --git a/index.md b/index.md index 167c6ea1c338..8309e6e0910f 100644 --- a/index.md +++ b/index.md @@ -10,7 +10,7 @@ End-of-life (EOL) and support information is [often hard to track, or very badly endoflife.date documents EOL dates and support lifecycles for various products. endoflife.date aggregates data from various sources and presents it in an understandable and -succinct manner. It also makes the data available using an [easily accessible API](https://endoflife.date/docs/api) +succinct manner. It also makes the data available using an [easily accessible API](/docs/api/v1/) and has iCalendar support. endoflife.date currently tracks {{ site.pages | where: "layout", "product" | size }} products. @@ -40,7 +40,7 @@ If you maintain release information for a product (end-of-life dates or support also have a [set of recommendations](/recommendations) along with a checklist on some best practices for publishing this information. -And do not hesitate to [play with our API](https://endoflife.date/docs/api). Here are a few awesome +And do not hesitate to [play with our API](/docs/api/v1/). Here are a few awesome tools that already did it: [norwegianblue](https://github.com/hugovk/norwegianblue), [end_of_life](https://github.com/MatheusRich/end_of_life), and [cicada](https://github.com/mcandre/cicada). Find more on