diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index a59688a4..0cb622cf 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -61,7 +61,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} continue-on-error: true # commit even if the data was not fully updated - run: python update.py + run: python update.py -p 'website/products' - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v6 diff --git a/README.md b/README.md index d6681efc..e1046ca8 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Common Release Data for various projects in a consistent and easy-to-parse forma ## Currently Updated -As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automatically tracked releases: +As of 2025-06-28, 316 of the 383 products tracked by endoflife.date have automatically tracked releases: | Product | Permalink | Auto | Method(s) | |---------|-----------|------|-----------| @@ -31,18 +31,18 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Alpine Linux | [`/alpine-linux`](https://endoflife.date/alpine-linux) | ✔️ | git, release_table | | Amazon CDK | [`/amazon-cdk`](https://endoflife.date/amazon-cdk) | ✔️ | git | | Amazon Corretto | [`/amazon-corretto`](https://endoflife.date/amazon-corretto) | ✔️ | github_releases | -| Amazon EKS | [`/amazon-eks`](https://endoflife.date/amazon-eks) | ✔️ | custom, release_table | +| Amazon EKS | [`/amazon-eks`](https://endoflife.date/amazon-eks) | ✔️ | amazon-eks, release_table | | Amazon Glue | [`/amazon-glue`](https://endoflife.date/amazon-glue) | ❌ | | | Amazon Linux | [`/amazon-linux`](https://endoflife.date/amazon-linux) | ✔️ | docker_hub | -| Amazon Neptune | [`/amazon-neptune`](https://endoflife.date/amazon-neptune) | ✔️ | custom, release_table | -| Amazon RDS for MariaDB | [`/amazon-rds-mariadb`](https://endoflife.date/amazon-rds-mariadb) | ✔️ | custom, release_table | -| Amazon RDS for MySQL | [`/amazon-rds-mysql`](https://endoflife.date/amazon-rds-mysql) | ✔️ | custom, release_table | -| Amazon RDS for PostgreSQL | [`/amazon-rds-postgresql`](https://endoflife.date/amazon-rds-postgresql) | ✔️ | custom, release_table | +| Amazon Neptune | [`/amazon-neptune`](https://endoflife.date/amazon-neptune) | ✔️ | amazon-neptune, release_table | +| Amazon RDS for MariaDB | [`/amazon-rds-mariadb`](https://endoflife.date/amazon-rds-mariadb) | ✔️ | rds, release_table | +| Amazon RDS for MySQL | [`/amazon-rds-mysql`](https://endoflife.date/amazon-rds-mysql) | ✔️ | rds, release_table | +| Amazon RDS for PostgreSQL | [`/amazon-rds-postgresql`](https://endoflife.date/amazon-rds-postgresql) | ✔️ | rds, release_table | | Android OS | [`/android`](https://endoflife.date/android) | ❌ | | | Angular | [`/angular`](https://endoflife.date/angular) | ✔️ | git, release_table | | AngularJS | [`/angularjs`](https://endoflife.date/angularjs) | ✔️ | npm | -| Ansible-core | [`/ansible-core`](https://endoflife.date/ansible-core) | ✔️ | git, release_table | | Ansible | [`/ansible`](https://endoflife.date/ansible) | ✔️ | pypi | +| Ansible-core | [`/ansible-core`](https://endoflife.date/ansible-core) | ✔️ | git, release_table | | antiX Linux | [`/antix`](https://endoflife.date/antix) | ✔️ | distrowatch | | Apache ActiveMQ | [`/apache-activemq`](https://endoflife.date/apache-activemq) | ✔️ | git | | Apache Airflow | [`/apache-airflow`](https://endoflife.date/apache-airflow) | ✔️ | pypi, release_table | @@ -55,7 +55,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Apache Groovy | [`/apache-groovy`](https://endoflife.date/apache-groovy) | ✔️ | maven | | Apache Hadoop | [`/apache-hadoop`](https://endoflife.date/apache-hadoop) | ✔️ | git | | Apache Hop | [`/apache-hop`](https://endoflife.date/apache-hop) | ✔️ | maven | -| Apache HTTP Server | [`/apache-http-server`](https://endoflife.date/apache-http-server) | ✔️ | custom | +| Apache HTTP Server | [`/apache-http-server`](https://endoflife.date/apache-http-server) | ✔️ | apache-http-server | | Apache Kafka | [`/apache-kafka`](https://endoflife.date/apache-kafka) | ✔️ | git, release_table | | Apache Lucene | [`/apache-lucene`](https://endoflife.date/apache-lucene) | ✔️ | maven | | Apache Maven | [`/apache-maven`](https://endoflife.date/apache-maven) | ✔️ | maven | @@ -63,14 +63,14 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Apache Pulsar | [`/apache-pulsar`](https://endoflife.date/apache-pulsar) | ✔️ | github_releases, release_table | | Apache Spark | [`/apache-spark`](https://endoflife.date/apache-spark) | ✔️ | git | | Apache Struts | [`/apache-struts`](https://endoflife.date/apache-struts) | ✔️ | maven | -| Apache Subversion | [`/apache-subversion`](https://endoflife.date/apache-subversion) | ✔️ | custom | +| Apache Subversion | [`/apache-subversion`](https://endoflife.date/apache-subversion) | ✔️ | apache-subversion | | API Platform | [`/api-platform`](https://endoflife.date/api-platform) | ✔️ | git | | Apple tvOS | [`/tvos`](https://endoflife.date/tvos) | ✔️ | apple | | Apple Watch | [`/apple-watch`](https://endoflife.date/apple-watch) | ❌ | | | ArangoDB | [`/arangodb`](https://endoflife.date/arangodb) | ✔️ | git | | Argo CD | [`/argo-cd`](https://endoflife.date/argo-cd) | ✔️ | git | -| Artifactory | [`/artifactory`](https://endoflife.date/artifactory) | ✔️ | custom | -| AWS Lambda | [`/aws-lambda`](https://endoflife.date/aws-lambda) | ✔️ | custom | +| Artifactory | [`/artifactory`](https://endoflife.date/artifactory) | ✔️ | artifactory | +| AWS Lambda | [`/aws-lambda`](https://endoflife.date/aws-lambda) | ✔️ | aws-lambda | | Azul Zulu | [`/azul-zulu`](https://endoflife.date/azul-zulu) | ❌ | | | Azure DevOps Server | [`/azure-devops-server`](https://endoflife.date/azure-devops-server) | ❌ | | | Azure Kubernetes Service | [`/azure-kubernetes-service`](https://endoflife.date/azure-kubernetes-service) | ❌ | | @@ -88,14 +88,14 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | caddy | [`/caddy`](https://endoflife.date/caddy) | ✔️ | git | | CakePHP | [`/cakephp`](https://endoflife.date/cakephp) | ✔️ | git | | Calico | [`/calico`](https://endoflife.date/calico) | ✔️ | git | -| CentOS Stream | [`/centos-stream`](https://endoflife.date/centos-stream) | ❌ | | | CentOS | [`/centos`](https://endoflife.date/centos) | ❌ | | +| CentOS Stream | [`/centos-stream`](https://endoflife.date/centos-stream) | ❌ | | | Centreon | [`/centreon`](https://endoflife.date/centreon) | ✔️ | git, release_table | | cert-manager | [`/cert-manager`](https://endoflife.date/cert-manager) | ✔️ | git | | CFEngine | [`/cfengine`](https://endoflife.date/cfengine) | ✔️ | git | -| Chef Infra Client | [`/chef-infra-client`](https://endoflife.date/chef-infra-client) | ✔️ | custom | -| Chef Infra Server | [`/chef-infra-server`](https://endoflife.date/chef-infra-server) | ✔️ | custom | -| Chef InSpec | [`/chef-inspec`](https://endoflife.date/chef-inspec) | ✔️ | custom | +| Chef Infra Client | [`/chef-infra-client`](https://endoflife.date/chef-infra-client) | ✔️ | chef-infra | +| Chef Infra Server | [`/chef-infra-server`](https://endoflife.date/chef-infra-server) | ✔️ | chef-infra | +| Chef InSpec | [`/chef-inspec`](https://endoflife.date/chef-inspec) | ✔️ | chef-inspec | | Citrix Virtual Apps and Desktops | [`/citrix-vad`](https://endoflife.date/citrix-vad) | ❌ | | | CKEditor | [`/ckeditor`](https://endoflife.date/ckeditor) | ❌ | | | ClamAV | [`/clamav`](https://endoflife.date/clamav) | ✔️ | git | @@ -110,12 +110,12 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Contao | [`/contao`](https://endoflife.date/contao) | ✔️ | git | | Contour | [`/contour`](https://endoflife.date/contour) | ✔️ | git | | Control-M | [`/controlm`](https://endoflife.date/controlm) | ❌ | | -| Google Container-Optimized OS (COS) | [`/cos`](https://endoflife.date/cos) | ✔️ | custom | -| Couchbase Server | [`/couchbase-server`](https://endoflife.date/couchbase-server) | ✔️ | custom, release_table | +| Google Container-Optimized OS (COS) | [`/cos`](https://endoflife.date/cos) | ✔️ | cos | +| Couchbase Server | [`/couchbase-server`](https://endoflife.date/couchbase-server) | ✔️ | couchbase-server, release_table | | Craft CMS | [`/craft-cms`](https://endoflife.date/craft-cms) | ✔️ | git, release_table | | dbt Core | [`/dbt-core`](https://endoflife.date/dbt-core) | ✔️ | git | | DaoCloud Enterprise | [`/dce`](https://endoflife.date/dce) | ❌ | | -| Debian | [`/debian`](https://endoflife.date/debian) | ✔️ | custom, release_table | +| Debian | [`/debian`](https://endoflife.date/debian) | ✔️ | debian, release_table | | Deno | [`/deno`](https://endoflife.date/deno) | ✔️ | git | | Dependency-Track | [`/dependency-track`](https://endoflife.date/dependency-track) | ✔️ | git | | Devuan | [`/devuan`](https://endoflife.date/devuan) | ✔️ | distrowatch | @@ -142,7 +142,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Fedora Linux | [`/fedora`](https://endoflife.date/fedora) | ✔️ | distrowatch | | FFmpeg | [`/ffmpeg`](https://endoflife.date/ffmpeg) | ✔️ | git | | FileMaker Platform | [`/filemaker`](https://endoflife.date/filemaker) | ✔️ | release_table | -| Firefox | [`/firefox`](https://endoflife.date/firefox) | ✔️ | custom | +| Firefox | [`/firefox`](https://endoflife.date/firefox) | ✔️ | firefox | | Fluent Bit | [`/fluent-bit`](https://endoflife.date/fluent-bit) | ✔️ | git | | Flux | [`/flux`](https://endoflife.date/flux) | ✔️ | git | | Forgejo | [`/forgejo`](https://endoflife.date/forgejo) | ✔️ | git, release_table | @@ -150,34 +150,34 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | FreeBSD | [`/freebsd`](https://endoflife.date/freebsd) | ❌ | | | Gatekeeper | [`/gatekeeper`](https://endoflife.date/gatekeeper) | ✔️ | git | | Gerrit | [`/gerrit`](https://endoflife.date/gerrit) | ✔️ | git | -| Glasgow Haskell Compiler (GHC) | [`/ghc`](https://endoflife.date/ghc) | ✔️ | custom, git | +| Glasgow Haskell Compiler (GHC) | [`/ghc`](https://endoflife.date/ghc) | ✔️ | git | | GitLab | [`/gitlab`](https://endoflife.date/gitlab) | ✔️ | git, release_table | | Go | [`/go`](https://endoflife.date/go) | ✔️ | git | | GoAccess | [`/goaccess`](https://endoflife.date/goaccess) | ✔️ | git | | Godot | [`/godot`](https://endoflife.date/godot) | ✔️ | git | -| Google Kubernetes Engine | [`/google-kubernetes-engine`](https://endoflife.date/google-kubernetes-engine) | ✔️ | custom | +| Google Kubernetes Engine | [`/google-kubernetes-engine`](https://endoflife.date/google-kubernetes-engine) | ✔️ | google-kubernetes-engine | | Google Nexus | [`/google-nexus`](https://endoflife.date/google-nexus) | ❌ | | | Gorilla Toolkit | [`/gorilla`](https://endoflife.date/gorilla) | ❌ | | -| GraalVM | [`/graalvm`](https://endoflife.date/graalvm) | ✔️ | custom | +| GraalVM Community Edition | [`/graalvm-ce`](https://endoflife.date/graalvm-ce) | ✔️ | graalvm | | Gradle | [`/gradle`](https://endoflife.date/gradle) | ✔️ | git | -| Grafana Loki | [`/grafana-loki`](https://endoflife.date/grafana-loki) | ✔️ | git | | Grafana | [`/grafana`](https://endoflife.date/grafana) | ✔️ | github_releases, release_table | +| Grafana Loki | [`/grafana-loki`](https://endoflife.date/grafana-loki) | ✔️ | git | | Grails Framework | [`/grails`](https://endoflife.date/grails) | ✔️ | git | | Graylog | [`/graylog`](https://endoflife.date/graylog) | ✔️ | git | | Greenlight | [`/greenlight`](https://endoflife.date/greenlight) | ✔️ | git | | Grunt | [`/grunt`](https://endoflife.date/grunt) | ✔️ | git | | GStreamer | [`/gstreamer`](https://endoflife.date/gstreamer) | ✔️ | git | | Guzzle | [`/guzzle`](https://endoflife.date/guzzle) | ✔️ | git | -| HAProxy | [`/haproxy`](https://endoflife.date/haproxy) | ✔️ | custom | +| HAProxy | [`/haproxy`](https://endoflife.date/haproxy) | ✔️ | haproxy | | Harbor | [`/harbor`](https://endoflife.date/harbor) | ✔️ | git | | Hashicorp Packer | [`/hashicorp-packer`](https://endoflife.date/hashicorp-packer) | ✔️ | git | | Hashicorp Vault | [`/hashicorp-vault`](https://endoflife.date/hashicorp-vault) | ✔️ | git | | Apache HBase | [`/hbase`](https://endoflife.date/hbase) | ✔️ | git | -| IBM AIX | [`/ibm-aix`](https://endoflife.date/ibm-aix) | ✔️ | custom, release_table | +| IBM AIX | [`/ibm-aix`](https://endoflife.date/ibm-aix) | ✔️ | ibm-aix, release_table | | IBM iSeries | [`/ibm-i`](https://endoflife.date/ibm-i) | ✔️ | release_table | | IBM Semeru Runtime | [`/ibm-semeru-runtime`](https://endoflife.date/ibm-semeru-runtime) | ✔️ | github_releases, release_table | -| Icinga Web | [`/icinga-web`](https://endoflife.date/icinga-web) | ✔️ | git | | Icinga | [`/icinga`](https://endoflife.date/icinga) | ✔️ | git | +| Icinga Web | [`/icinga-web`](https://endoflife.date/icinga-web) | ✔️ | git | | Intel Processors | [`/intel-processors`](https://endoflife.date/intel-processors) | ❌ | | | Internet Explorer | [`/internet-explorer`](https://endoflife.date/internet-explorer) | ❌ | | | Ionic Framework | [`/ionic`](https://endoflife.date/ionic) | ✔️ | git, release_table | @@ -192,8 +192,8 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | JHipster | [`/jhipster`](https://endoflife.date/jhipster) | ✔️ | npm | | Jira Software | [`/jira-software`](https://endoflife.date/jira-software) | ✔️ | atlassian_eol, atlassian_versions | | Joomla! | [`/joomla`](https://endoflife.date/joomla) | ✔️ | git | -| jQuery UI | [`/jquery-ui`](https://endoflife.date/jquery-ui) | ✔️ | git | | jQuery | [`/jquery`](https://endoflife.date/jquery) | ✔️ | git | +| jQuery UI | [`/jquery-ui`](https://endoflife.date/jquery-ui) | ✔️ | git | | JReleaser | [`/jreleaser`](https://endoflife.date/jreleaser) | ✔️ | maven | | Julia | [`/julia`](https://endoflife.date/julia) | ✔️ | git | | KDE Plasma | [`/kde-plasma`](https://endoflife.date/kde-plasma) | ✔️ | git | @@ -204,21 +204,22 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Kirby | [`/kirby`](https://endoflife.date/kirby) | ✔️ | git | | Kong Gateway | [`/kong-gateway`](https://endoflife.date/kong-gateway) | ✔️ | git | | Kotlin | [`/kotlin`](https://endoflife.date/kotlin) | ✔️ | github_releases | +| Kubernetes | [`/kubernetes`](https://endoflife.date/kubernetes) | ✔️ | git | | Kubernetes CSI Node Driver Registrar | [`/kubernetes-csi-node-driver-registrar`](https://endoflife.date/kubernetes-csi-node-driver-registrar) | ✔️ | git | | Kubernetes Node Feature Discovery | [`/kubernetes-node-feature-discovery`](https://endoflife.date/kubernetes-node-feature-discovery) | ✔️ | github_releases | -| Kubernetes | [`/kubernetes`](https://endoflife.date/kubernetes) | ✔️ | git | -| Kuma | [`/kuma`](https://endoflife.date/kuma) | ✔️ | git | +| Kuma | [`/kuma`](https://endoflife.date/kuma) | ✔️ | git, kuma | | Kyverno | [`/kyverno`](https://endoflife.date/kyverno) | ✔️ | git | | Laravel | [`/laravel`](https://endoflife.date/laravel) | ✔️ | git, release_table | | LDAP Account Manager | [`/ldap-account-manager`](https://endoflife.date/ldap-account-manager) | ✔️ | git | -| LibreOffice | [`/libreoffice`](https://endoflife.date/libreoffice) | ✔️ | custom | +| LibreOffice | [`/libreoffice`](https://endoflife.date/libreoffice) | ✔️ | libreoffice | | LineageOS | [`/lineageos`](https://endoflife.date/lineageos) | ❌ | | | Linux Kernel | [`/linux`](https://endoflife.date/linux) | ✔️ | github_tags | | Linux Mint | [`/linuxmint`](https://endoflife.date/linuxmint) | ✔️ | release_table | +| Liquibase | [`/liquibase`](https://endoflife.date/liquibase) | ✔️ | maven | | Apache Log4j | [`/log4j`](https://endoflife.date/log4j) | ✔️ | maven | | Logstash | [`/logstash`](https://endoflife.date/logstash) | ✔️ | git | -| Looker | [`/looker`](https://endoflife.date/looker) | ✔️ | custom | -| Lua | [`/lua`](https://endoflife.date/lua) | ✔️ | custom | +| Looker | [`/looker`](https://endoflife.date/looker) | ✔️ | looker | +| Lua | [`/lua`](https://endoflife.date/lua) | ✔️ | lua | | Apple macOS | [`/macos`](https://endoflife.date/macos) | ✔️ | apple | | Mageia | [`/mageia`](https://endoflife.date/mageia) | ✔️ | distrowatch | | Magento | [`/magento`](https://endoflife.date/magento) | ✔️ | git | @@ -244,7 +245,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Neo4j | [`/neo4j`](https://endoflife.date/neo4j) | ✔️ | git, release_table | | Neos | [`/neos`](https://endoflife.date/neos) | ✔️ | git | | NetApp ONTAP | [`/netapp-ontap`](https://endoflife.date/netapp-ontap) | ❌ | | -| NetBSD | [`/netbsd`](https://endoflife.date/netbsd) | ✔️ | custom | +| NetBSD | [`/netbsd`](https://endoflife.date/netbsd) | ✔️ | netbsd | | Nextcloud | [`/nextcloud`](https://endoflife.date/nextcloud) | ✔️ | git, release_table | | Next.js | [`/nextjs`](https://endoflife.date/nextjs) | ✔️ | npm | | Nexus Repository | [`/nexus`](https://endoflife.date/nexus) | ✔️ | git, release_table | @@ -254,6 +255,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Node.js | [`/nodejs`](https://endoflife.date/nodejs) | ✔️ | git | | Nokia Mobile | [`/nokia`](https://endoflife.date/nokia) | ❌ | | | Nomad | [`/nomad`](https://endoflife.date/nomad) | ✔️ | git | +| Notepad++ | [`/notepad-plus-plus`](https://endoflife.date/notepad-plus-plus) | ✔️ | git | | NumPy | [`/numpy`](https://endoflife.date/numpy) | ✔️ | pypi | | Nutanix AOS | [`/nutanix-aos`](https://endoflife.date/nutanix-aos) | ✔️ | nutanix | | Nutanix Files | [`/nutanix-files`](https://endoflife.date/nutanix-files) | ✔️ | nutanix | @@ -277,7 +279,8 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | OPNsense | [`/opnsense`](https://endoflife.date/opnsense) | ✔️ | git | | Oracle APEX | [`/oracle-apex`](https://endoflife.date/oracle-apex) | ✔️ | release_table | | Oracle Database | [`/oracle-database`](https://endoflife.date/oracle-database) | ✔️ | release_table | -| Oracle JDK | [`/oracle-jdk`](https://endoflife.date/oracle-jdk) | ✔️ | custom, release_table | +| Oracle GraalVM | [`/oracle-graalvm`](https://endoflife.date/oracle-graalvm) | ✔️ | graalvm, release_table | +| Oracle JDK | [`/oracle-jdk`](https://endoflife.date/oracle-jdk) | ✔️ | oracle-jdk, release_table | | Oracle Linux | [`/oracle-linux`](https://endoflife.date/oracle-linux) | ✔️ | distrowatch | | Oracle Solaris | [`/oracle-solaris`](https://endoflife.date/oracle-solaris) | ❌ | | | oVirt | [`/ovirt`](https://endoflife.date/ovirt) | ✔️ | git | @@ -286,12 +289,12 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Palo Alto Networks PAN-OS | [`/panos`](https://endoflife.date/panos) | ✔️ | release_table | | PCI-DSS | [`/pci-dss`](https://endoflife.date/pci-dss) | ❌ | | | Perl | [`/perl`](https://endoflife.date/perl) | ✔️ | git | -| PHP | [`/php`](https://endoflife.date/php) | ✔️ | custom | +| PHP | [`/php`](https://endoflife.date/php) | ✔️ | php | | phpBB | [`/phpbb`](https://endoflife.date/phpbb) | ✔️ | git | | phpMyAdmin | [`/phpmyadmin`](https://endoflife.date/phpmyadmin) | ✔️ | git | -| Google Pixel Watch | [`/pixel-watch`](https://endoflife.date/pixel-watch) | ❌ | | | Google Pixel | [`/pixel`](https://endoflife.date/pixel) | ❌ | | -| Plesk | [`/plesk`](https://endoflife.date/plesk) | ✔️ | custom | +| Google Pixel Watch | [`/pixel-watch`](https://endoflife.date/pixel-watch) | ❌ | | +| Plesk | [`/plesk`](https://endoflife.date/plesk) | ✔️ | plesk | | Plone | [`/plone`](https://endoflife.date/plone) | ✔️ | git, release_table | | pnpm | [`/pnpm`](https://endoflife.date/pnpm) | ✔️ | npm | | Podman | [`/podman`](https://endoflife.date/podman) | ✔️ | git | @@ -313,39 +316,40 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | RabbitMQ | [`/rabbitmq`](https://endoflife.date/rabbitmq) | ✔️ | git | | Rancher | [`/rancher`](https://endoflife.date/rancher) | ✔️ | git | | Raspberry Pi | [`/raspberry-pi`](https://endoflife.date/raspberry-pi) | ❌ | | -| React Native | [`/react-native`](https://endoflife.date/react-native) | ✔️ | npm | | React | [`/react`](https://endoflife.date/react) | ✔️ | npm | +| React Native | [`/react-native`](https://endoflife.date/react-native) | ✔️ | npm | | Red Hat build of OpenJDK | [`/redhat-build-of-openjdk`](https://endoflife.date/redhat-build-of-openjdk) | ✔️ | redhat_lifecycles | -| Red Hat JBoss Enterprise Application Platform | [`/redhat-jboss-eap`](https://endoflife.date/redhat-jboss-eap) | ✔️ | custom, redhat_lifecycles | -| Red Hat OpenShift | [`/red-hat-openshift`](https://endoflife.date/red-hat-openshift) | ✔️ | custom | -| Red Hat Satellite | [`/redhat-satellite`](https://endoflife.date/redhat-satellite) | ✔️ | custom | +| Red Hat JBoss Enterprise Application Platform | [`/redhat-jboss-eap`](https://endoflife.date/redhat-jboss-eap) | ✔️ | red-hat-jboss-eap-7, red-hat-jboss-eap-8, redhat_lifecycles | +| Red Hat OpenShift | [`/red-hat-openshift`](https://endoflife.date/red-hat-openshift) | ✔️ | red-hat-openshift | +| Red Hat Satellite | [`/redhat-satellite`](https://endoflife.date/redhat-satellite) | ✔️ | red-hat-satellite | | Redis | [`/redis`](https://endoflife.date/redis) | ✔️ | git, release_table | | Redmine | [`/redmine`](https://endoflife.date/redmine) | ✔️ | git | | Red Hat Enterprise Linux | [`/rhel`](https://endoflife.date/rhel) | ✔️ | redhat_lifecycles | | Robo | [`/robo`](https://endoflife.date/robo) | ✔️ | git, release_table | | Rocket.Chat | [`/rocket-chat`](https://endoflife.date/rocket-chat) | ✔️ | git | -| Rocky Linux | [`/rocky-linux`](https://endoflife.date/rocky-linux) | ✔️ | custom, release_table | +| Rocky Linux | [`/rocky-linux`](https://endoflife.date/rocky-linux) | ✔️ | release_table, rocky-linux | +| ROS | [`/ros`](https://endoflife.date/ros) | ✔️ | ros | | ROS 2 | [`/ros-2`](https://endoflife.date/ros-2) | ❌ | | -| ROS | [`/ros`](https://endoflife.date/ros) | ❌ | | | Roundcube Webmail | [`/roundcube`](https://endoflife.date/roundcube) | ✔️ | git | | rtpengine | [`/rtpengine`](https://endoflife.date/rtpengine) | ✔️ | git | -| Ruby on Rails | [`/rails`](https://endoflife.date/rails) | ✔️ | git | | Ruby | [`/ruby`](https://endoflife.date/ruby) | ✔️ | git | +| Ruby on Rails | [`/rails`](https://endoflife.date/rails) | ✔️ | git | | Rust | [`/rust`](https://endoflife.date/rust) | ✔️ | git | | Salt | [`/salt`](https://endoflife.date/salt) | ✔️ | git, release_table | +| Samsung Galaxy Tab | [`/samsung-galaxy-tab`](https://endoflife.date/samsung-galaxy-tab) | ✔️ | samsung-security | | Samsung Galaxy Watch | [`/samsung-galaxy-watch`](https://endoflife.date/samsung-galaxy-watch) | ❌ | | -| Samsung Mobile | [`/samsung-mobile`](https://endoflife.date/samsung-mobile) | ✔️ | custom | +| Samsung Mobile | [`/samsung-mobile`](https://endoflife.date/samsung-mobile) | ✔️ | samsung-security | | SapMachine | [`/sapmachine`](https://endoflife.date/sapmachine) | ✔️ | github_releases | | Scala | [`/scala`](https://endoflife.date/scala) | ✔️ | github_releases | | Microsoft SharePoint | [`/sharepoint`](https://endoflife.date/sharepoint) | ❌ | | | Shopware | [`/shopware`](https://endoflife.date/shopware) | ✔️ | git | | Silverstripe CMS | [`/silverstripe`](https://endoflife.date/silverstripe) | ✔️ | git, release_table | | Slackware Linux | [`/slackware`](https://endoflife.date/slackware) | ✔️ | distrowatch | -| SUSE Linux Enterprise Server | [`/sles`](https://endoflife.date/sles) | ❌ | | +| SUSE Linux Enterprise Server | [`/sles`](https://endoflife.date/sles) | ✔️ | sles | | Apache Solr | [`/solr`](https://endoflife.date/solr) | ✔️ | git | | SonarQube | [`/sonar`](https://endoflife.date/sonar) | ✔️ | git | | Sourcegraph | [`/sourcegraph`](https://endoflife.date/sourcegraph) | ✔️ | git | -| Splunk | [`/splunk`](https://endoflife.date/splunk) | ✔️ | custom | +| Splunk | [`/splunk`](https://endoflife.date/splunk) | ✔️ | splunk | | Spring Boot | [`/spring-boot`](https://endoflife.date/spring-boot) | ✔️ | git, release_table | | Spring Framework | [`/spring-framework`](https://endoflife.date/spring-framework) | ✔️ | git, release_table | | SQLite | [`/sqlite`](https://endoflife.date/sqlite) | ✔️ | git | @@ -362,7 +366,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Apache Tomcat | [`/tomcat`](https://endoflife.date/tomcat) | ✔️ | maven | | Traefik | [`/traefik`](https://endoflife.date/traefik) | ✔️ | git, release_table | | Twig | [`/twig`](https://endoflife.date/twig) | ✔️ | git | -| TYPO3 | [`/typo3`](https://endoflife.date/typo3) | ✔️ | custom | +| TYPO3 | [`/typo3`](https://endoflife.date/typo3) | ✔️ | typo3 | | Ubuntu | [`/ubuntu`](https://endoflife.date/ubuntu) | ✔️ | distrowatch | | Umbraco CMS | [`/umbraco`](https://endoflife.date/umbraco) | ✔️ | git, release_table | | Unity | [`/unity`](https://endoflife.date/unity) | ❌ | | @@ -372,10 +376,10 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Veeam Backup & Replication | [`/veeam-backup-and-replication`](https://endoflife.date/veeam-backup-and-replication) | ✔️ | veeam | | Veeam Backup for Microsoft 365 | [`/veeam-backup-for-microsoft-365`](https://endoflife.date/veeam-backup-for-microsoft-365) | ✔️ | veeam | | Veeam ONE | [`/veeam-one`](https://endoflife.date/veeam-one) | ✔️ | veeam | -| VirtualBox | [`/virtualbox`](https://endoflife.date/virtualbox) | ❌ | | +| VirtualBox | [`/virtualbox`](https://endoflife.date/virtualbox) | ✔️ | virtualbox | | Apple visionOS | [`/visionos`](https://endoflife.date/visionos) | ✔️ | apple | | Visual COBOL | [`/visual-cobol`](https://endoflife.date/visual-cobol) | ❌ | | -| Microsoft Visual Studio | [`/visual-studio`](https://endoflife.date/visual-studio) | ✔️ | custom | +| Microsoft Visual Studio | [`/visual-studio`](https://endoflife.date/visual-studio) | ✔️ | visual-studio | | Vitess | [`/vitess`](https://endoflife.date/vitess) | ✔️ | git | | VMware Cloud Foundation | [`/vmware-cloud-foundation`](https://endoflife.date/vmware-cloud-foundation) | ❌ | | | VMware ESXi | [`/esxi`](https://endoflife.date/esxi) | ❌ | | @@ -389,19 +393,19 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat | Apple watchOS | [`/watchos`](https://endoflife.date/watchos) | ✔️ | apple | | Weakforced | [`/weakforced`](https://endoflife.date/weakforced) | ✔️ | git | | WeeChat | [`/weechat`](https://endoflife.date/weechat) | ✔️ | git | +| Microsoft Windows | [`/windows`](https://endoflife.date/windows) | ❌ | | | Microsoft Windows Embedded | [`/windows-embedded`](https://endoflife.date/windows-embedded) | ❌ | | | Microsoft Nano Server | [`/windows-nano-server`](https://endoflife.date/windows-nano-server) | ❌ | | -| Microsoft Windows Server Core | [`/windows-server-core`](https://endoflife.date/windows-server-core) | ❌ | | | Microsoft Windows Server | [`/windows-server`](https://endoflife.date/windows-server) | ❌ | | -| Microsoft Windows | [`/windows`](https://endoflife.date/windows) | ❌ | | +| Microsoft Windows Server Core | [`/windows-server-core`](https://endoflife.date/windows-server-core) | ❌ | | | Wireshark | [`/wireshark`](https://endoflife.date/wireshark) | ✔️ | git | | WordPress | [`/wordpress`](https://endoflife.date/wordpress) | ✔️ | git | | XCP-ng | [`/xcp-ng`](https://endoflife.date/xcp-ng) | ✔️ | git, release_table | | Yarn | [`/yarn`](https://endoflife.date/yarn) | ✔️ | npm | | Yocto Project | [`/yocto`](https://endoflife.date/yocto) | ✔️ | git | -| Zabbix | [`/zabbix`](https://endoflife.date/zabbix) | ✔️ | git, release_table | +| Zabbix | [`/zabbix`](https://endoflife.date/zabbix) | ✔️ | git | | Zentyal | [`/zentyal`](https://endoflife.date/zentyal) | ✔️ | release_table | -| Zerto | [`/zerto`](https://endoflife.date/zerto) | ❌ | | +| Zerto | [`/zerto`](https://endoflife.date/zerto) | ✔️ | release_table | | Apache ZooKeeper | [`/zookeeper`](https://endoflife.date/zookeeper) | ✔️ | maven | This table has been generated by [report.py](/report.py). diff --git a/latest.py b/latest.py index e70505bc..3136bf03 100644 --- a/latest.py +++ b/latest.py @@ -11,7 +11,9 @@ from ruamel.yaml import YAML from ruamel.yaml.representer import RoundTripRepresenter from ruamel.yaml.resolver import Resolver +from src.common.endoflife import list_products from src.common.gha import GitHubOutput +from src.common.releasedata import DATA_DIR """ Updates the `release`, `latest` and `latestReleaseDate` property in automatically updated pages @@ -243,7 +245,6 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='Update product releases.') parser.add_argument('product', nargs='?', help='restrict update to the given product') parser.add_argument('-p', '--product-dir', required=True, help='path to the product directory') - parser.add_argument('-d', '--data-dir', required=True, help='path to the release data directory') parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose logging') args = parser.parse_args() @@ -257,10 +258,11 @@ if __name__ == "__main__": RoundTripRepresenter.ignore_aliases = lambda x, y: True # NOQA: ARG005 products_dir = Path(args.product_dir) - product_names = [args.product] if args.product else [p.stem for p in products_dir.glob("*.md")] + data_dir = Path(__file__).resolve().parent / DATA_DIR + products = list_products(products_dir, args.product) github_output = GitHubOutput("warning") with github_output: - for product_name in sorted(product_names): - logging.debug(f"Processing {product_name}") - update_product(product_name, products_dir, Path(args.data_dir), github_output) + for product in products: + logging.debug(f"Processing {product.name}") + update_product(product.name, products_dir, data_dir, github_output) diff --git a/report.py b/report.py index a6234afd..7f32efde 100644 --- a/report.py +++ b/report.py @@ -1,20 +1,28 @@ +import argparse import time +from pathlib import Path from src.common import endoflife -products = endoflife.list_products() -count_auto = len([product for product in products if product.auto_configs()]) +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Create report on product automation.') + parser.add_argument('-p', '--product-dir', required=True, help='path to the product directory') + args = parser.parse_args() -print(f"As of {time.strftime('%Y-%m-%d')}, {count_auto} of the {len(products)} products" - f" tracked by endoflife.date have automatically tracked releases:") -print() -print('| Product | Permalink | Auto | Method(s) |') -print('|---------|-----------|------|-----------|') -for product in products: - title = product.get_title() - permalink = product.get_permalink() - auto = '✔️' if product.has_auto_configs() else '❌' - methods = ', '.join(sorted({config.method for config in product.auto_configs()})) - print(f"| {title} | [`{permalink}`](https://endoflife.date{permalink}) | {auto} | {methods} |") -print() -print('This table has been generated by [report.py](/report.py).') + products_dir = Path(args.product_dir) + products = endoflife.list_products(products_dir) + count_auto = len([product for product in products if product.auto_configs()]) + + print(f"As of {time.strftime('%Y-%m-%d')}, {count_auto} of the {len(products)} products" + f" tracked by endoflife.date have automatically tracked releases:") + print() + print('| Product | Permalink | Auto | Method(s) |') + print('|---------|-----------|------|-----------|') + for product in products: + title = product.get_title() + permalink = product.get_permalink() + auto = '✔️' if product.has_auto_configs() else '❌' + methods = ', '.join(sorted({config.method for config in product.auto_configs()})) + print(f"| {title} | [`{permalink}`](https://endoflife.date{permalink}) | {auto} | {methods} |") + print() + print('This table has been generated by [report.py](/report.py).') diff --git a/src/amazon-eks.py b/src/amazon-eks.py index 57c68250..28052296 100644 --- a/src/amazon-eks.py +++ b/src/amazon-eks.py @@ -1,11 +1,11 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches EKS versions from AWS docs. Now that AWS no longer publishes docs on GitHub, we use the Web Archive to get the older versions.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/amazon-neptune.py b/src/amazon-neptune.py index 2f49f3ee..18c27633 100644 --- a/src/amazon-neptune.py +++ b/src/amazon-neptune.py @@ -1,10 +1,10 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches Amazon Neptune versions from its RSS feed on docs.aws.amazon.com.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: rss = http.fetch_xml(config.url) diff --git a/src/apache-http-server.py b/src/apache-http-server.py index f64a44d6..416df001 100644 --- a/src/apache-http-server.py +++ b/src/apache-http-server.py @@ -1,10 +1,10 @@ -from common import dates, endoflife, releasedata +from common import dates, releasedata from common.git import Git """Fetches Apache HTTP Server versions and release date from its git repository by looking at the STATUS file of each ..x branch.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: git = Git(config.url) git.setup() diff --git a/src/apache-subversion.py b/src/apache-subversion.py index a7c167b5..cdc1647b 100644 --- a/src/apache-subversion.py +++ b/src/apache-subversion.py @@ -1,8 +1,8 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/apple.py b/src/apple.py index 71fb4218..3c8a9546 100644 --- a/src/apple.py +++ b/src/apple.py @@ -2,7 +2,7 @@ import logging import re from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches and parses version and release date information from Apple's support website.""" @@ -22,7 +22,7 @@ URLS = [ DATE_PATTERN = re.compile(r"\b\d+\s[A-Za-z]+\s\d+\b") -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: # URLs are cached to avoid rate limiting by support.apple.com. soups = [BeautifulSoup(response.text, features="html5lib") for response in http.fetch_urls(URLS)] diff --git a/src/artifactory.py b/src/artifactory.py index 4fe0820e..2c4dfe35 100644 --- a/src/artifactory.py +++ b/src/artifactory.py @@ -1,10 +1,10 @@ from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches Artifactory versions from https://jfrog.com, using requests_html because JavaScript is needed to render the page.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: content = http.fetch_javascript_url(config.url, wait_until = 'networkidle') soup = BeautifulSoup(content, 'html.parser') diff --git a/src/atlassian_eol.py b/src/atlassian_eol.py index 0626d22d..877078ae 100644 --- a/src/atlassian_eol.py +++ b/src/atlassian_eol.py @@ -1,7 +1,7 @@ import logging from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches EOL dates from Atlassian EOL page. @@ -9,7 +9,7 @@ This script takes a selector argument which is the product title identifier on t `AtlassianSupportEndofLifePolicy-JiraSoftware`. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: content = http.fetch_javascript_url(config.url) soup = BeautifulSoup(content, features="html5lib") diff --git a/src/atlassian_versions.py b/src/atlassian_versions.py index aa27b828..3511e4b9 100644 --- a/src/atlassian_versions.py +++ b/src/atlassian_versions.py @@ -1,5 +1,5 @@ from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches versions from Atlassian download-archives pages. @@ -7,7 +7,7 @@ This script takes a single argument which is the url of the product's download-a `https://www.atlassian.com/software/confluence/download-archives`. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: content = http.fetch_javascript_url(config.url, wait_until='networkidle') soup = BeautifulSoup(content, 'html5lib') diff --git a/src/aws-lambda.py b/src/aws-lambda.py index fabea99b..afbe3d0a 100644 --- a/src/aws-lambda.py +++ b/src/aws-lambda.py @@ -1,10 +1,10 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches AWS lambda runtimes with their support / EOL dates from https://docs.aws.amazon.com.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/cgit.py b/src/cgit.py index 317b9b9a..43ef1805 100644 --- a/src/cgit.py +++ b/src/cgit.py @@ -1,9 +1,9 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches versions from repositories managed with cgit, such as the Linux kernel repository. Ideally we would want to use the git repository directly, but cgit-managed repositories don't support partial clone.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url + '/refs/tags') diff --git a/src/chef-infra.py b/src/chef-infra.py index aab5b663..5243fd2e 100644 --- a/src/chef-infra.py +++ b/src/chef-infra.py @@ -1,4 +1,4 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata from common.git import Git """Fetch released versions from docs.chef.io and retrieve their date from GitHub. @@ -7,7 +7,7 @@ docs.chef.io needs to be scraped because not all tagged versions are actually re More context on https://github.com/endoflife-date/endoflife.date/pull/4425#discussion_r1447932411. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) released_versions = [h2.get('id') for h2 in html.find_all('h2', id=True) if h2.get('id')] diff --git a/src/chef-inspec.py b/src/chef-inspec.py index 10584f80..db6b50c9 100644 --- a/src/chef-inspec.py +++ b/src/chef-inspec.py @@ -1,4 +1,4 @@ -from common import dates, endoflife, github, http, releasedata +from common import dates, github, http, releasedata """Fetch released versions from docs.chef.io and retrieve their date from GitHub. docs.chef.io needs to be scraped because not all tagged versions are actually released. @@ -6,7 +6,7 @@ docs.chef.io needs to be scraped because not all tagged versions are actually re More context on https://github.com/endoflife-date/endoflife.date/pull/4425#discussion_r1447932411. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) released_versions = [h2.get('id') for h2 in html.find_all('h2', id=True) if h2.get('id')] diff --git a/src/coldfusion.py b/src/coldfusion.py index 31116112..c84d43fb 100644 --- a/src/coldfusion.py +++ b/src/coldfusion.py @@ -1,6 +1,6 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches versions from Adobe ColdFusion release notes on helpx.adobe.com. @@ -21,7 +21,7 @@ FIXED_VERSIONS = { "2023.0.0": dates.date(2022, 5, 16), # https://coldfusion.adobe.com/2023/05/coldfusion2023-release/ } -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/common/endoflife.py b/src/common/endoflife.py index 0f2bff37..5c5a9669 100644 --- a/src/common/endoflife.py +++ b/src/common/endoflife.py @@ -1,8 +1,5 @@ -import itertools import logging -import os import re -import sys from datetime import datetime from pathlib import Path @@ -15,8 +12,6 @@ DEFAULT_VERSION_REGEX = r"^v?(?P[1-9]\d*)\.(?P\d+)(\.(?P\d+ DEFAULT_VERSION_PATTERN = re.compile(DEFAULT_VERSION_REGEX) DEFAULT_VERSION_TEMPLATE = "{{major}}{% if minor %}.{{minor}}{% if patch %}.{{patch}}{% if tiny %}.{{tiny}}{% endif %}{% endif %}{% endif %}" -PRODUCTS_PATH = Path(os.environ.get("PRODUCTS_PATH", "website/products")) - class AutoConfig: def __init__(self, product: str, data: dict) -> None: @@ -58,9 +53,9 @@ class AutoConfig: class ProductFrontmatter: - def __init__(self, name: str) -> None: - self.name: str = name - self.path: Path = PRODUCTS_PATH / f"{name}.md" + def __init__(self, path: Path) -> None: + self.path: Path = path + self.name: str = path.stem self.data = None if self.path.is_file(): @@ -109,37 +104,19 @@ class ProductFrontmatter: return None -def list_products(products_filter: str = None) -> list[ProductFrontmatter]: - """Return a list of products that are using the same given update method.""" +def list_products(products_dir: Path, product_name: str = None) -> list[ProductFrontmatter]: + product_names = [product_name] if product_name else sorted([p.stem for p in products_dir.glob("*.md")]) + products = [] - - for product_file in sorted(PRODUCTS_PATH.glob("*.md")): - product_name = product_file.stem - if products_filter and product_name != products_filter: - continue - + for product_name in product_names: try: - products.append(ProductFrontmatter(product_name)) + products.append(ProductFrontmatter(products_dir / f"{product_name}.md")) except Exception as e: logging.exception(f"failed to load product data for {product_name}: {e}") return products -def list_configs(products_filter: str = None, methods_filter: str = None, urls_filter: str = None) -> list[AutoConfig]: - """Return a list of auto configs, filtering by product name, method, and URL.""" - products = list_products(products_filter) - configs_by_product = [p.auto_configs(methods_filter, urls_filter) for p in products] - return list(itertools.chain.from_iterable(configs_by_product)) # flatten the list of lists - - -def list_configs_from_argv() -> list[AutoConfig]: - products_filter = sys.argv[1] if len(sys.argv) > 1 else None - methods_filter = sys.argv[2] if len(sys.argv) > 1 else None - urls_filter = sys.argv[3] if len(sys.argv) > 2 else None - return list_configs(products_filter, methods_filter, urls_filter) - - def to_identifier(s: str) -> str: """Convert a string to a valid endoflife.date identifier.""" identifier = s.strip().lower() diff --git a/src/common/releasedata.py b/src/common/releasedata.py index c8edcbd8..3171227d 100644 --- a/src/common/releasedata.py +++ b/src/common/releasedata.py @@ -1,15 +1,16 @@ +import argparse import json import logging -import os +import sys from datetime import datetime, timezone from pathlib import Path from types import TracebackType from typing import Optional, Type -# Do not update the format: it's also used to declare groups in the GitHub Actions logs. -logging.basicConfig(format="%(message)s", level=logging.INFO) +from . import endoflife -VERSIONS_PATH = Path(os.environ.get("VERSIONS_PATH", "releases")) +SRC_DIR = Path('src') +DATA_DIR = Path('releases') class ProductUpdateError(Exception): @@ -108,7 +109,7 @@ class ProductVersion: class ProductData: def __init__(self, name: str) -> None: self.name: str = name - self.versions_path: Path = VERSIONS_PATH / f"{name}.json" + self.versions_path: Path = DATA_DIR / f"{name}.json" self.releases = {} self.versions: dict[str, ProductVersion] = {} self.updated = False @@ -190,3 +191,21 @@ class ProductData: def __repr__(self) -> str: return self.name + + +def list_configs_from_argv() -> list[endoflife.AutoConfig]: + return parse_argv()[1] + +def parse_argv() -> tuple[endoflife.ProductFrontmatter, list[endoflife.AutoConfig]]: + parser = argparse.ArgumentParser(description=sys.argv[0]) + parser.add_argument('-p', '--product', required=True, help='path to the product') + parser.add_argument('-m', '--method', required=True, help='method to filter by') + parser.add_argument('-u', '--url', required=True, help='url to filter by') + parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose logging') + args = parser.parse_args() + + # Do not update the format: it's also used to declare groups in the GitHub Actions logs. + logging.basicConfig(format="%(message)s", level=(logging.DEBUG if args.verbose else logging.INFO)) + + product = endoflife.ProductFrontmatter(Path(args.product)) + return product, product.auto_configs(args.method, args.url) diff --git a/src/cos.py b/src/cos.py index ab276f65..ee8536b0 100644 --- a/src/cos.py +++ b/src/cos.py @@ -2,7 +2,7 @@ import datetime import re from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata MILESTONE_PATTERN = re.compile(r'COS \d+ LTS') VERSION_PATTERN = re.compile(r"^(cos-\d+-\d+-\d+-\d+)") @@ -14,7 +14,7 @@ def parse_date(date_text: str) -> datetime: return dates.parse_date(date_text) -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: main = http.fetch_url(config.url) main_soup = BeautifulSoup(main.text, features="html5lib") diff --git a/src/couchbase-server.py b/src/couchbase-server.py index 1b320178..a8d130b5 100644 --- a/src/couchbase-server.py +++ b/src/couchbase-server.py @@ -1,7 +1,7 @@ import logging from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches versions from release notes of each minor version on docs.couchbase.com. @@ -16,7 +16,7 @@ MANUAL_VERSIONS = { "7.2.0": dates.date(2023, 6, 1), # https://www.couchbase.com/blog/couchbase-capella-spring-release-72/ } -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(f"{config.url}/current/install/install-intro.html") diff --git a/src/debian.py b/src/debian.py index b54bdefc..802854cf 100644 --- a/src/debian.py +++ b/src/debian.py @@ -1,7 +1,7 @@ from pathlib import Path from subprocess import run -from common import dates, endoflife, releasedata +from common import dates, releasedata from common.git import Git """Fetch Debian versions by parsing news in www.debian.org source repository.""" @@ -40,7 +40,7 @@ def extract_point_versions(p: releasedata.ProductData, repo_dir: Path) -> None: (date, version) = line.split(' ') p.declare_version(version, dates.parse_date(date)) -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: git = Git(config.url) git.setup() diff --git a/src/distrowatch.py b/src/distrowatch.py index bed61c02..4f585bf5 100644 --- a/src/distrowatch.py +++ b/src/distrowatch.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(f"https://distrowatch.com/index.php?distribution={config.url}") diff --git a/src/docker_hub.py b/src/docker_hub.py index b33b4bb4..38965173 100644 --- a/src/docker_hub.py +++ b/src/docker_hub.py @@ -17,6 +17,6 @@ def fetch_releases(p: releasedata.ProductData, c: endoflife.AutoConfig, url: str fetch_releases(p, c, data["next"]) -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: fetch_releases(product_data, config, f"https://hub.docker.com/v2/repositories/{config.url}/tags?page_size=100&page=1") diff --git a/src/firefox.py b/src/firefox.py index da570406..88194b1b 100644 --- a/src/firefox.py +++ b/src/firefox.py @@ -1,7 +1,7 @@ import urllib.parse from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetch Firefox versions with their dates from https://www.mozilla.org/. @@ -20,7 +20,7 @@ The script will need to be updated if someday those conditions are not met.""" MAX_VERSIONS_LIMIT = 100 -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: releases_page = http.fetch_url(config.url) releases_soup = BeautifulSoup(releases_page.text, features="html5lib") diff --git a/src/ghc-wiki.py b/src/ghc-wiki.py index fc8e4be0..47f2a3ed 100644 --- a/src/ghc-wiki.py +++ b/src/ghc-wiki.py @@ -14,7 +14,7 @@ References: import re from typing import Any, Generator, Iterator -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata def parse_markdown_tables(lineiter: Iterator[str]) -> Generator[list[list[Any]], Any, None]: @@ -50,7 +50,7 @@ def maybe_markdown_table_row(line: str) -> list[str] | None: return None return [x.strip() for x in line.strip('|').split('|')] -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product: resp = http.fetch_url(config.url) resp.raise_for_status() diff --git a/src/git.py b/src/git.py index 35c5c024..99febdf6 100644 --- a/src/git.py +++ b/src/git.py @@ -1,9 +1,9 @@ -from common import dates, endoflife, releasedata +from common import dates, releasedata from common.git import Git """Fetches versions from tags in a git repository. This replace the old update.rb script.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: git = Git(config.url) git.setup(bare=True) diff --git a/src/github_releases.py b/src/github_releases.py index 730e9116..075f8eee 100644 --- a/src/github_releases.py +++ b/src/github_releases.py @@ -1,11 +1,11 @@ -from common import dates, endoflife, github, releasedata +from common import dates, github, releasedata """Fetches versions from GitHub releases using the GraphQL API and the GitHub CLI. Note: GraphQL API and GitHub CLI are used because it's simpler: no need to manage pagination and authentication. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: for release in github.fetch_releases(config.url): if release.is_prerelease: diff --git a/src/github_tags.py b/src/github_tags.py index 872857b0..06d451da 100644 --- a/src/github_tags.py +++ b/src/github_tags.py @@ -1,11 +1,11 @@ -from common import dates, endoflife, github, releasedata +from common import dates, github, releasedata """Fetches versions from GitHub tags using the GraphQL API and the GitHub CLI. Note: GraphQL API and GitHub CLI are used because it's simpler: no need to manage pagination and authentication. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: for tag in github.fetch_tags(config.url): version_str = tag.name diff --git a/src/google-kubernetes-engine.py b/src/google-kubernetes-engine.py index 4a637032..d1284df8 100644 --- a/src/google-kubernetes-engine.py +++ b/src/google-kubernetes-engine.py @@ -1,6 +1,6 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata # https://regex101.com/r/zPxBqT/1 VERSION_PATTERN = re.compile(r"\d.\d+\.\d+-gke\.\d+") @@ -11,7 +11,7 @@ URL_BY_PRODUCT = { "google-kubernetes-engine-rapid": "https://cloud.google.com/kubernetes-engine/docs/release-notes-rapid", } -for config in endoflife.list_configs_from_argv(): # noqa: B007 multiple JSON produced for historical reasons +for config in releasedata.list_configs_from_argv(): # noqa: B007 multiple JSON produced for historical reasons for product_name, url in URL_BY_PRODUCT.items(): with releasedata.ProductData(product_name) as product_data: html = http.fetch_html(url) diff --git a/src/graalvm.py b/src/graalvm.py index cffe2bd9..0e94fdb4 100644 --- a/src/graalvm.py +++ b/src/graalvm.py @@ -1,8 +1,8 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) table_selector = config.data.get("table_selector", "#previous-releases + table").strip() diff --git a/src/haproxy.py b/src/haproxy.py index c2413bca..3c3db896 100644 --- a/src/haproxy.py +++ b/src/haproxy.py @@ -1,11 +1,11 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata CYCLE_PATTERN = re.compile(r"^(\d+\.\d+)/$") DATE_AND_VERSION_PATTERN = re.compile(r"^(\d{4})/(\d{2})/(\d{2})\s+:\s+(\d+\.\d+\.\d.?)$") # https://regex101.com/r/1JCnFC/1 -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: # First, get all minor releases from the download page download_html = http.fetch_html(config.url) diff --git a/src/ibm-aix.py b/src/ibm-aix.py index 37eba3ff..3d69b33b 100644 --- a/src/ibm-aix.py +++ b/src/ibm-aix.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/kuma.py b/src/kuma.py index ea6b7fd7..67857405 100644 --- a/src/kuma.py +++ b/src/kuma.py @@ -1,6 +1,6 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetch version data for Kuma from https://raw.githubusercontent.com/kumahq/kuma/master/versions.yml. """ @@ -9,7 +9,7 @@ RELEASE_FIELD = 'release' RELEASE_DATE_FIELD = 'releaseDate' EOL_FIELD = 'endOfLifeDate' -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: versions_data = http.fetch_yaml(config.url) diff --git a/src/libreoffice.py b/src/libreoffice.py index fb55d343..99c08a03 100644 --- a/src/libreoffice.py +++ b/src/libreoffice.py @@ -1,10 +1,10 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches LibreOffice versions from https://downloadarchive.documentfoundation.org/libreoffice/old/""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/looker.py b/src/looker.py index b7d34652..daa678c0 100644 --- a/src/looker.py +++ b/src/looker.py @@ -1,14 +1,14 @@ import re from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetch Looker versions from the Google Cloud release notes RSS feed. """ ANNOUNCEMENT_PATTERN = re.compile(r"includes\s+the\s+following\s+changes", re.IGNORECASE) -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: rss = http.fetch_xml(config.url) diff --git a/src/lua.py b/src/lua.py index f1ec5f82..6af31b8a 100644 --- a/src/lua.py +++ b/src/lua.py @@ -1,13 +1,13 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches Lua releases from lua.org.""" RELEASED_AT_PATTERN = re.compile(r"Lua\s*(?P\d+\.\d+)\s*was\s*released\s*on\s*(?P\d+\s*\w+\s*\d{4})") VERSION_PATTERN = re.compile(r"(?P\d+\.\d+\.\d+),\s*released\s*on\s*(?P\d+\s*\w+\s*\d{4})") -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url, features = 'html.parser') page_text = html.text # HTML is broken, no way to parse it with beautifulsoup diff --git a/src/maven.py b/src/maven.py index d6b430b6..18149bd0 100644 --- a/src/maven.py +++ b/src/maven.py @@ -1,8 +1,8 @@ from datetime import datetime, timezone -from common import endoflife, http, releasedata +from common import http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: start = 0 group_id, artifact_id = config.url.split("/") diff --git a/src/netbsd.py b/src/netbsd.py index 0115b455..054e8be3 100644 --- a/src/netbsd.py +++ b/src/netbsd.py @@ -1,10 +1,10 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches NetBSD versions and EOL information from https://www.netbsd.org/.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/npm.py b/src/npm.py index b6d132d8..adbe21c2 100644 --- a/src/npm.py +++ b/src/npm.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: data = http.fetch_json(f"https://registry.npmjs.org/{config.url}") for version_str in data["versions"]: diff --git a/src/nutanix.py b/src/nutanix.py index 61d0cd80..ea6cee3d 100644 --- a/src/nutanix.py +++ b/src/nutanix.py @@ -1,8 +1,8 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetch Nutanix products versions from https://portal.nutanix.com/api/v1.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: data = http.fetch_json(f"https://portal.nutanix.com/api/v1/eol/find?type={config.url}") diff --git a/src/oracle-jdk.py b/src/oracle-jdk.py index b7da1346..0157d629 100644 --- a/src/oracle-jdk.py +++ b/src/oracle-jdk.py @@ -1,11 +1,11 @@ from bs4 import BeautifulSoup -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetch Java versions from https://www.java.com/releases/. This script is using requests-html because the page needs JavaScript to render correctly.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: content = http.fetch_javascript_url(config.url, wait_until='networkidle') soup = BeautifulSoup(content, 'html.parser') diff --git a/src/pan-os.py b/src/pan-os.py index 755bd069..b834657f 100644 --- a/src/pan-os.py +++ b/src/pan-os.py @@ -1,8 +1,8 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches pan-os versions from https://github.com/mrjcap/panos-versions/.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: versions = http.fetch_json(config.url) diff --git a/src/php.py b/src/php.py index ad325757..507c3d94 100644 --- a/src/php.py +++ b/src/php.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: # Fetch major versions latest_by_major = http.fetch_url(config.url).json() diff --git a/src/plesk.py b/src/plesk.py index fd3a755e..5b2a1434 100644 --- a/src/plesk.py +++ b/src/plesk.py @@ -1,11 +1,11 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches versions from Plesk's change log. Only 18.0.20.3 and later will be picked up, as the format of the change log for 18.0.20 and 18.0.19 are different and there is no entry for GA of version 18.0.18 and older.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/pypi.py b/src/pypi.py index a8648cad..e11b2ecb 100644 --- a/src/pypi.py +++ b/src/pypi.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: data = http.fetch_json(f"https://pypi.org/pypi/{config.url}/json") diff --git a/src/rds.py b/src/rds.py index 99112ff9..fe20d4ac 100644 --- a/src/rds.py +++ b/src/rds.py @@ -1,6 +1,6 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches Amazon RDS versions from the version management pages on AWS docs. @@ -8,7 +8,7 @@ Pages parsed by this script are expected to have version tables with a version i in the third column (usually named 'RDS release date'). """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/red-hat-jboss-eap-7.py b/src/red-hat-jboss-eap-7.py index fbfb3356..3cb7425a 100644 --- a/src/red-hat-jboss-eap-7.py +++ b/src/red-hat-jboss-eap-7.py @@ -1,10 +1,10 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches RedHat JBoss EAP version data for JBoss 7""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/red-hat-jboss-eap-8.py b/src/red-hat-jboss-eap-8.py index fc486aab..766920d8 100644 --- a/src/red-hat-jboss-eap-8.py +++ b/src/red-hat-jboss-eap-8.py @@ -1,10 +1,10 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches the latest RedHat JBoss EAP version data for JBoss 8.0""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: xml = http.fetch_xml(config.url) diff --git a/src/red-hat-openshift.py b/src/red-hat-openshift.py index 44cb2520..5e96f43f 100644 --- a/src/red-hat-openshift.py +++ b/src/red-hat-openshift.py @@ -1,6 +1,6 @@ import re -from common import dates, endoflife, releasedata +from common import dates, releasedata from common.git import Git """Fetches Red Hat OpenShift versions from the documentation's git repository""" @@ -10,7 +10,7 @@ VERSION_AND_DATE_PATTERN = re.compile( re.MULTILINE, ) -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: git = Git(config.url) git.setup() diff --git a/src/red-hat-satellite.py b/src/red-hat-satellite.py index 0e16b9a5..0effc876 100644 --- a/src/red-hat-satellite.py +++ b/src/red-hat-satellite.py @@ -1,12 +1,12 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches Satellite versions from access.redhat.com. A few of the older versions, such as 'Satellite 6.1 GA Release (Build 6.1.1)', were ignored because too hard to parse.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/redhat_lifecycles.py b/src/redhat_lifecycles.py index 823c2645..c3adbb8f 100644 --- a/src/redhat_lifecycles.py +++ b/src/redhat_lifecycles.py @@ -1,7 +1,7 @@ import logging import urllib.parse -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches EOL dates from the Red Hat Product Life Cycle Data API. @@ -17,7 +17,7 @@ class Mapping: def get_field_for(self, phase_name: str) -> str | None: return self.fields_by_phase.get(phase_name.lower(), None) -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: name = urllib.parse.quote(config.url) mapping = Mapping(config.data["fields"]) diff --git a/src/release_table.py b/src/release_table.py index 377ab24c..c216bfd2 100644 --- a/src/release_table.py +++ b/src/release_table.py @@ -150,7 +150,7 @@ class Field: return f"{self.name}({self.column})" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: render_javascript = config.data.get("render_javascript", False) render_javascript_click_selector = config.data.get("render_javascript_click_selector", None) diff --git a/src/rhel.py b/src/rhel.py index 4e37ef6e..46a1bb88 100644 --- a/src/rhel.py +++ b/src/rhel.py @@ -1,11 +1,11 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata # https://regex101.com/r/877ibq/1 VERSION_PATTERN = re.compile(r"RHEL (?P\d)(\. ?(?P\d+))?(( Update (?P\d))| GA)?") -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/rocky-linux.py b/src/rocky-linux.py index 20e5bfa1..562a396b 100644 --- a/src/rocky-linux.py +++ b/src/rocky-linux.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: response = http.fetch_url(config.url) for line in response.text.strip().split('\n'): diff --git a/src/ros.py b/src/ros.py index 153028a4..3ebf118b 100644 --- a/src/ros.py +++ b/src/ros.py @@ -1,8 +1,8 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/samsung-security.py b/src/samsung-security.py index 5d949358..4e9000ff 100644 --- a/src/samsung-security.py +++ b/src/samsung-security.py @@ -12,9 +12,9 @@ it retains the date and use it as the model's EOL date. TODAY = dates.today() -for config in endoflife.list_configs_from_argv(): +frontmatter, configs = releasedata.parse_argv() +for config in configs: with releasedata.ProductData(config.product) as product_data: - frontmatter = endoflife.ProductFrontmatter(product_data.name) frontmatter_release_names = frontmatter.get_release_names() # Copy EOL dates from frontmatter to product data diff --git a/src/sles.py b/src/sles.py index ac0642b9..cc474bfc 100644 --- a/src/sles.py +++ b/src/sles.py @@ -1,8 +1,8 @@ import logging -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/splunk.py b/src/splunk.py index 4e8dce2b..20f0cf0f 100644 --- a/src/splunk.py +++ b/src/splunk.py @@ -1,6 +1,6 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata VERSION_DATE_PATTERN = re.compile(r"Splunk Enterprise (?P\d+\.\d+(?:\.\d+)*) was (?:first )?released on (?P\w+\s\d\d?,\s\d{4})\.", re.MULTILINE) @@ -29,7 +29,7 @@ def get_latest_minor_versions(versions: list[str]) -> list[str]: return latest_versions -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/typo3.py b/src/typo3.py index 27a0773c..f2e1da7e 100644 --- a/src/typo3.py +++ b/src/typo3.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: data = http.fetch_json(config.url) for v in data: diff --git a/src/unity.py b/src/unity.py index af1a283d..4df337f2 100644 --- a/src/unity.py +++ b/src/unity.py @@ -1,4 +1,4 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches the Unity LTS releases from the Unity website. Non-LTS releases are not listed there, so this automation is only partial. @@ -16,7 +16,7 @@ Note that it was assumed that: The script will need to be updated if someday those conditions are not met.""" -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/unrealircd.py b/src/unrealircd.py index 54701f88..a63d8b55 100644 --- a/src/unrealircd.py +++ b/src/unrealircd.py @@ -1,10 +1,10 @@ import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata DATE_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}") -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: wikicode = http.fetch_markdown(config.url) diff --git a/src/veeam.py b/src/veeam.py index 43ff0d4a..fcd9bdd8 100644 --- a/src/veeam.py +++ b/src/veeam.py @@ -1,7 +1,7 @@ import logging import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches Veeam products versions from https://www.veeam.com. @@ -9,7 +9,7 @@ This script takes a single argument which is the url of the versions page on htt such as `https://www.veeam.com/kb2680`. """ -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/virtualbox.py b/src/virtualbox.py index 85106f63..b2fed7a0 100644 --- a/src/virtualbox.py +++ b/src/virtualbox.py @@ -1,13 +1,13 @@ import logging import re -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata """Fetches releases from VirtualBox download page.""" EOL_REGEX = re.compile(r"^\(no longer supported, support ended (?P\d{4}/\d{2})\)$") -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/src/visual-studio.py b/src/visual-studio.py index f2c2fb09..3c5a92e4 100644 --- a/src/visual-studio.py +++ b/src/visual-studio.py @@ -1,6 +1,6 @@ -from common import dates, endoflife, http, releasedata +from common import dates, http, releasedata -for config in endoflife.list_configs_from_argv(): +for config in releasedata.list_configs_from_argv(): with releasedata.ProductData(config.product) as product_data: html = http.fetch_html(config.url) diff --git a/update.py b/update.py index 5e0d9940..9f9ac2c4 100644 --- a/update.py +++ b/update.py @@ -1,3 +1,4 @@ +import argparse import json import logging import subprocess @@ -9,9 +10,7 @@ from deepdiff import DeepDiff from src.common.endoflife import AutoConfig, ProductFrontmatter, list_products from src.common.gha import GitHubGroup, GitHubOutput, GitHubStepSummary - -SRC_DIR = Path('src') -DATA_DIR = Path('releases') +from src.common.releasedata import DATA_DIR, SRC_DIR class ScriptExecutionSummary: @@ -66,7 +65,7 @@ def install_playwright() -> None: def __delete_data(product: ProductFrontmatter) -> None: - release_data_path = DATA_DIR / f"{product.name}.json" + release_data_path = Path(__file__).resolve().parent / DATA_DIR / f"{product.name}.json" if not release_data_path.exists() or product.is_auto_update_cumulative(): return @@ -75,19 +74,23 @@ def __delete_data(product: ProductFrontmatter) -> None: def __revert_data(product: ProductFrontmatter) -> None: - release_data_path = DATA_DIR / f"{product.name}.json" + release_data_path = Path(__file__).resolve().parent / DATA_DIR / f"{product.name}.json" # check=False because the command fails if the file did not exist before subprocess.run(f'git checkout HEAD -- {release_data_path}', timeout=10, check=False, shell=True) logging.warning(f"reverted changes in {release_data_path}") def __run_script(product: ProductFrontmatter, config: AutoConfig, summary: ScriptExecutionSummary) -> bool: - script = SRC_DIR / config.script + script = Path(__file__).resolve().parent / SRC_DIR / config.script logging.info(f"start running {script} for {config}") start = time.perf_counter() + # timeout is handled in child scripts - child = subprocess.run([sys.executable, script, config.product, str(config.method), str(config.url)]) + script_args = [sys.executable, script, "-p", product.path, "-m", str(config.method), "-u", str(config.url)] + script_args = script_args + ["-v"] if logging.getLogger().isEnabledFor(logging.DEBUG) else script_args + child = subprocess.run(script_args) + success = child.returncode == 0 elapsed_seconds = time.perf_counter() - start @@ -98,13 +101,10 @@ def __run_script(product: ProductFrontmatter, config: AutoConfig, summary: Scrip return success -def run_scripts(summary: GitHubStepSummary, product_filter: str) -> bool: +def run_scripts(summary: GitHubStepSummary, products: list[ProductFrontmatter]) -> bool: exec_summary = ScriptExecutionSummary() - with GitHubGroup("Load Product Data"): - product_list = list_products(product_filter) - - for product in product_list: + for product in products: if not product.has_auto_configs(): continue @@ -166,24 +166,31 @@ def generate_commit_message(old_content: dict[Path, dict], new_content: dict[Pat commit_message.println("") summary.println("") +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Update product releases.') + parser.add_argument('product', nargs='?', help='restrict update to the given product') + parser.add_argument('-p', '--product-dir', required=True, help='path to the product directory') + parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose logging') + args = parser.parse_args() -logging.basicConfig(format="%(message)s", level=logging.INFO) -p_filter = sys.argv[1] if len(sys.argv) > 1 else None - - -with GitHubStepSummary() as step_summary: + logging.basicConfig(format=logging.BASIC_FORMAT, level=(logging.DEBUG if args.verbose else logging.INFO)) install_playwright() - some_script_failed = run_scripts(step_summary, p_filter) - updated_products = get_updated_products() - step_summary.println("## Update summary\n") - if updated_products: - new_files_content = load_products_json(updated_products) - subprocess.run('git stash --all --quiet', timeout=10, check=True, shell=True) - old_files_content = load_products_json(updated_products) - subprocess.run('git stash pop --quiet', timeout=10, check=True, shell=True) - generate_commit_message(old_files_content, new_files_content, step_summary) - else: - step_summary.println("No update") + products_dir = Path(args.product_dir) + products_list = list_products(products_dir, args.product) -sys.exit(1 if some_script_failed else 0) + with GitHubStepSummary() as step_summary: + some_script_failed = run_scripts(step_summary, products_list) + updated_products = get_updated_products() + + step_summary.println("## Update summary\n") + if updated_products: + new_files_content = load_products_json(updated_products) + subprocess.run('git stash --all --quiet', timeout=10, check=True, shell=True) + old_files_content = load_products_json(updated_products) + subprocess.run('git stash pop --quiet', timeout=10, check=True, shell=True) + generate_commit_message(old_files_content, new_files_content, step_summary) + else: + step_summary.println("No update") + + sys.exit(1 if some_script_failed else 0)