Versioning ========== Versioning is an integral part of the code review and Continuous Integration (CI) process, and releasing a new version of a component will be an extremely lightweight and frequent event. The ``VERSION`` File -------------------- The versioning model embraced by Edge Orchestrator is automated to optimize for development velocity, and uses a file named ``VERSION`` in the root of a repo, which contains a `Semantic Versioning (SemVer) <https://semver.org>`_ version number. It is manually updated by developers during the development process. This file as a source of truth around versioning, has the following benefits: 1. The ``VERSION`` file can be managed through the standard code review process, allowing developers to use a single process to create releases. When the contents of ``VERSION`` is a *released* version (with no suffix), on pull request merge automation creates git tags on the repository using the SemVer release specified in the ``VERSION`` file. Developers must decide whether to change the ``MAJOR``, ``MINOR``, or ``PATCH`` component based on two factors: * The API breakage requirements of SemVer (ie, breaking APIs == increase ``MAJOR``, adding APIs == increase ``MINOR``) * Whether space is needed to create a support branch (ie, increase ``MINOR`` when creating a support branch used tracking with the release manifest. 2. When a new PR is created, the CI checks that a tag has not been created with the contents of the ``VERSION`` file so that a git tag will only point back to a single commit, allowing problems or defects to be tracked back to the corresponding code revisions. This eliminates nonsensical and hard-to-answer questions like "Which version of 1.0.0 is this?". 3. Automation also allows setting the ``VERSION`` file to have an *unreleased* version that has a ``-dev`` or other suffix - this allows development between releases, as git tags are not created for these unreleased versions. A typical cadence is have the ``VERSION`` file change between ``dev`` -> ``release`` -> ``dev`` -> ``release`` during development. .. note:: The ``VERSION`` file starts with a number. The go versioning specific ``v`` prefix is not used in the ``VERSION`` file - git tags with the ``v`` prefix will be created by automation if required. Component Versioning Example ---------------------------- In this example, Intel has a component that is currently on the released ``0.3.0`` version (which has the contents of the ``VERSION`` file). To add features and fixes to the component, the following steps are taken: .. figure:: images/version_bump.drawio.svg :alt: Component Version Bump Component Version Bump 1. If it hasn't already happened, a pull request is created to change the ``VERSION`` file to increase to the next version and also add the ``-dev`` unreleased version suffix: Example: ``0.3.1-dev`` .. note:: Any of the ``MAJOR``, ``MINOR``, or ``PATCH`` versions could be changed, depending on SemVer and branching space requirements. 2. Finish the feature or fixes. Multiple features may go into this cycle. Prior to the release, perform any functional and component integration tests to validate functionality. 3. Submit a pull request that removes the ``-dev`` suffix from the ``VERSION`` so it becomes a released version: Example: ``0.3.1`` *Optionally*, submit another pull request that increases to the next unreleased version in preparation for future development: Example: ``0.3.2-dev`` Component Branching Example --------------------------- When a branch needs to be made for maintenance purposes, the typical flow used is as follows: .. figure:: images/branch.drawio.svg :alt: Component Branching Component Branching The general flow is as follows: 1. The main branch starts with an unreleased ``-dev`` version in the ``VERSION`` file. Both Features and Bug fixes can go into the main branch. Example: ``0.4.0-dev`` 2. When a Edge Orchestrator-wide release and `Feature Freeze <https://en.wikipedia.org/wiki/Freeze_(software_engineering)>`__ is happening, a branch needs to be created to support this release. As branches start with a specific tag, the ``VERSION`` file is changed to be a released SemVer version: Example: ``0.4.0`` - At this point, a support branch can be created, starting from the tag. You do not necessarily need to create support branches prior to when they are required, only when they are needed (this is called *Lazy Branching*) - Support branches are named ``release-MAJOR.MINOR``, and have branch protection rules to prevent accidental pushes. A protected branch can be created using these commands: .. code:: shell git checkout 0.4.0 git checkout -b release-0.4 git push origin release-0.4 3. Immediately after creating the tag, a space for the patch branch is created on the main branch, by updating the ``VERSION`` file to have a new unreleased ``-dev`` version that increments the ``MINOR`` version: Example: ``0.5.0-dev`` 4. A release validation cycle starts. As there may be patches created during validation while the ``0.4.0`` artifacts are being tested, the ``VERSION`` file is updated with the unreleased ``-dev`` version, and pushed to the support branch. This allows multiple fixes to be incorporated in the next patch release. Example: ``0.4.1-dev`` 5. There may be bugs discovered during validation, which require patches to fix. There are many strategies for creating and applying these fixes, but teams should generally pick one strategy and merge to a direction so the process is well understood within the team. - In this example, the team has chosen to start bug fixes on the main branch, and then cherry-pick to the support branches. The advantages of this are that testing on main and the support branch may result in more complete testing, and also that cherry picking from main to a support branch ensures that bug fixes are always applied to the main branch. - Alternatively, creating patches on the support branch and cherry-picking back to the main branch may be more expedient, but can require more care to ensure that the main branch is also suitably patched. - In rare cases, a bug fix may be needed only on the support branch, or two different non-cherry-picked patches for the same fix may be required (if the main branch is sufficiently different in code or workflow than the patch branch). In either cases, additional care must be taken so as not to introduce errors. - Cherry-picking must be done individually for every patch, as trying to cherry-pick from multiple patches or pull requests that have been squashed together may unintentionally introduce bugs or new features into a maintenance branch. 6. Once the support branch has received enough changes that it is ready for release, the ``VERSION`` is updated to drop the ``-dev`` unreleased suffix, which creates the ``0.4.1`` tag, and then immediately after that, a new ``0.4.2-dev`` tag is created for future bug fixes. Post-PR-Merge Actions ~~~~~~~~~~~~~~~~~~~~~ After passing code review and being merged, the post-merge CI job will run to create the Git tags and artifacts (binaries, container images, etc.). .. figure:: images/artifact-build.drawio.svg :alt: Artifact Build Artifact Build So that these artifacts can be traced back to the code that they were created from, the tag and commit information must be embedded in the artifacts - some examples of how this can be done: - In the Docker\* instance case, metadata in the `OCI Annotations <https://specs.opencontainers.org/image-spec/annotations/>`__ format must be added. - You can embed the version and other identifiers into the Go* binary when it is being built, by using the `ldflags -X <https://pkg.go.dev/cmd/link>`_ or `embed <https://pkg.go.dev/embed>`_ directives. Artifact generation processes in CI must integrate this information as a part of the CI process. Automation Scripts ------------------ Versioning automation scripts are maintained in the `orch-ci <https://github.com/open-edge-platform/orch-ci>`__ repository. - `version-check.sh <https://github.com/open-edge-platform/orch-ci/blob/main/scripts/version-check.sh>`__ The ``version-check`` script enforces versioning requirement and prevents submission of pull requests that contain an already-released Version. A common development pattern is to immediately create and commit another pull request that increments the ``VERSION`` file to contain an unreleased version after committing a released Version. Rebasing pull requests on top of released versions need changes to the version file to avoid being rejected in Jenkins\* CI. Additionally, automatic merge or rebase must be disabled on the project repository so that the jobs that ensure a changed version file are run on each pull request. Reverting a pull request has similar requirements - reverts must go to a non-released version to avoid the pre- and post- revert patches from having the same released version. - `version-tag.sh <https://github.com/open-edge-platform/orch-ci/blob/main/scripts/version-tag.sh>`__ On submission of a pull request, the ``version-tag`` script runs and if the version file contains a released version number, a git tag with that version is created on the project repository. Git tags are not created for unreleased versions. CI jobs that run the ``version-tag`` script must be configured to run post submit in the ``main`` branch and any release branches in the Jenkins file. There must be no manual tagging or moving of tags that use SemVer versions. Various non-SemVer vanity tagging of jobs may have been applied in the past, but future use is discouraged. Automation Q&A -------------- 1. Why does my commit fail on the next patch after incrementing the VERSION? You need to ensure that the sequence of versions followed does not have two commits with same VERSION. Here is an example of sequence: ``0.1.0-dev -> 1.0.0-dev -> 1.0.0 (Released SemVer) -> 1.0.1-dev (Dev patch) -> 1.0.1 (Release patch) or similar``. 2. Can I skip a sequence of version (Ex: 1.0.0 -> 1.0.2)? No, one must make sure that the next version is always incremented by one, of the previous one. If the version is incremented by two, the Version-Check job would fail because the previous parent (in the ex: 1.0.1) would not be found. 3. Can I have a patch for a previous release train after a new version is released? Yes. You can have a patch release for a VERSION that has a different release train. Example: - 1.1.0 -> 1.1.1 patch might happen after a 1.2.0 is released. 4. What are the different type of VERSION files supported? As of today, the following type of VERSION files can be used: - VERSION - package.json (specific to ``Node.js`` projects) - pom.xml (specific to Java/Maven projects) 5. Why did the Docker parent check fail? The Docker parent check is implemented to ensure the right base image is used in the Dockerfile. The check supports using a container where the parent image is the ``FROM scratch`` `Docker base image <https://docs.docker.com/build/building/base-images/>`_, or a `static distroless image with non-root <https://github.com/GoogleContainerTools/distroless>`__, or a released SemVer / non-released version with a specific SHA-256 hash. The check will fail if it’s not using a specific version or when a parent image is not found. 6. What are good Docker FROM lines? - ``FROM [--platform=<platform>] <image> [AS <name>]`` - ``FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]`` - ``FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]`` Optionally a name can be given to a new build stage by adding AS name to the FROM instruction. The tag or digest values are optional. If you omit either of them, the builder assumes a latest tag by default. The builder returns an error if it cannot find the tag value.