Development Guide

Submitting a pull request(PR)

  1. Fork the odo repository.

  2. Clone your fork:

    Note
    The following commands assume that you have the $GOPATH environment variable properly set. We highly recommend you place odo code into the $GOPATH.
    $ git clone https://github.com/<YOUR_GITHUB_USERNAME>/odo.git $GOPATH/src/github.com/openshift/odo
    $ cd $GOPATH/src/github.com/openshift/odo
    $ git remote add upstream 'https://github.com/openshift/odo'

    When cloning odo, the Windows terminal such as PowerShell or CMD may throw a Filename too long error. To avoid such an error, set your Git configuration as follows:

    $ git config --system core.longpaths true
  3. Create a branch, refer to the guidelines below in the sections below, and create a PR with your changes. If your PR is still in-progress, indicate this with a label or add WIP in your PR title.

    A PR must include:

    • Descriptive context that outlines what has been changed and why

    • A link to the active or open issue it fixes (if applicable)

Reviewing a PR

PR review process

  1. Once you submit a PR, the @openshift-ci-robot automatically requests two reviews from reviewers and suggests an approver based on OWNERS files.

  2. After a reviewer is satisfied with the changes he adds /lgtm (looks good to me) as a comment to the PR. This applies the lgtm label.

  3. The approver then reviews the PR and if satisfied, adds /approve as a comment to the PR. This applies the approve label.

    • After the PR has lgtm and approve labels and the required tests pass, the bot automatically merges the PR.

      Note
      If you are a maintainer and have write access to the odo repository, modify your git configuration so that you do not accidentally push to upstream:
      $ git remote set-url --push upstream no_push

What to look out for when reviewing a pull request:

  • Have tests been added?

  • Does the feature or fix work locally?

  • Is the code understandable, have comments been added to the code?

  • A PR should pass all the pre-submit tests, all request changes must be resolved, and needs at least two approving reviews. If you apply the /lgtm label before it meets this criteria, put it on hold with the /hold label immediately. You can use /lgtm cancel to cancel your /lgtm and use /hold cancel once you are ready to approve it. This especially applies to draft PRs.

  • Approvers can use /approve and /approve cancel to approve or hold their approval respectively.

About Prow

odo uses the Prow infrastucture for CI testing. * It uses OWNERS files to determine who can approve and lgtm a PR. * Prow has two levels of OWNERS, Approvers and Reviewers Approvers look for holistic acceptance criteria, including dependencies with other features, forward and backward compatibility, API and flag definitions, etc. In essence, the high levels of design Reviewers look for general code quality, correctness, sane software engineering, style, etc. In essence, the quality of the actual code itself.

  • Avoid merging the PR manually, unless it is an emergency and you have the required permissions). Prow’s tide component automatically merges the PR once all the conditions are met. It also ensures that post-submit tests (tests that run before merge) valiate the PR.

  • Use the command-help to see the list of possible bot commands.

Test Driven Development

We follow the Test Driven Development(TDD) workflow in our development process. You can read more about it here.

Unit tests

Unit tests for odo functions are written using package fake. This allows us to create a fake client, and then mock the API calls defined under OpenShift client-go and k8s client-go.

The tests are written in golang using the pkg/testing package.

Writing unit tests

  1. Identify the APIs used by the function to be tested.

  2. Initialize the fake client along with the relevant client sets. The following example explains the initialization of fake clients and the creation of fake objects.

    The function GetImageStreams in pkg/occlient.go fetches imagestream objects through the API:

    func (c *Client) GetImageStreams(namespace string) ([]imagev1.ImageStream, error) {
            imageStreamList, err := c.imageClient.ImageStreams(namespace).List(metav1.ListOptions{})
            if err != nil {
                    return nil, errors.Wrap(err, "unable to list imagestreams")
            }
            return imageStreamList.Items, nil
    }
    1. For writing the tests, start by initializing the fake client using the function FakeNew() which initializes the image clientset harnessed by GetImageStreams function:

      client, fkclientset := FakeNew()
    2. In the GetImageStreams functions, the list of imagestreams is fetched through the API. While using fake client, this list can be emulated using a PrependReactor interface:

       fkclientset.ImageClientset.PrependReactor("list", "imagestreams", func(action ktesting.Action) (bool, runtime.Object, error) {
               return true, fakeImageStreams(tt.args.name, tt.args.namespace), nil
           })

      The PrependReactor expects resource and verb to be passed in as arguments. Get this information by looking at the List function for fake imagestream:

      func (c *FakeImageStreams) List(opts v1.ListOptions) (result *image_v1.ImageStreamList, err error) {
              obj, err := c.Fake.Invokes(testing.NewListAction(imagestreamsResource, imagestreamsKind, c.ns, opts), &image_v1.ImageStreamList{})
          ...
      }
       func NewListAction(resource schema.GroupVersionResource, kind schema.GroupVersionKind, namespace string, opts interface{}) ListActionImpl {
              action := ListActionImpl{}
              action.Verb = "list"
              action.Resource = resource
              action.Kind = kind
              action.Namespace = namespace
              labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
              action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
               return action
      }

      The List function internally calls NewListAction defined in k8s.io/client-go/testing/actions.go. From these functions, we see that the resource and verb to be passed into the PrependReactor interface are imagestreams and list respectively.

      You can see the entire test function TestGetImageStream in pkg/occlient/occlient_test.go.

      Note
      You can use environment variable CUSTOM_HOMEDIR to specify a custom home directory. It can be used in environments where a user and home directory are not resolvable.
  3. In the case where functions fetch or create new objects through the APIs, add a reactor interface returning fake objects.

  4. Verify the objects returned.

Integration tests

Integration tests are used within odo. All tests can be found in the tests/ directory and can be called using functions within makefile. Also test directory comprises e2e scenario and a clean test template for reference.

Prerequisites:
  • A minishift or OpenShift environment with Service Catalog enabled:

    $ MINISHIFT_ENABLE_EXPERIMENTAL=y minishift start --extra-clusterup-flags "--enable=*,service-catalog,automation-service-broker,template-service-broker"
  • odo and oc binaries in $PATH.

Procedure:

To deploy an integration test:

  • For the entire integration suite use:

$ make test-e2e
  • For the generic tests use:

$ make test-generic
  • For the component tests use:

$ make test-cmp-e2e
  • For the service catalog tests use:

$ make test-service-e2e
  • For the e2e scenario test use:

$ make test-e2e-scenarios

You can run a subset of tests with ginkgo by using focused specs mechanism https://onsi.github.io/ginkgo/#focused-specs

Race conditions

Test failures during the execution of the integration tests do occur. For example, the following error has been encountered multiple times:

Operation cannot be fulfilled on deploymentconfigs.apps.openshift.io "component-app": the object has been modified; please apply your changes to the latest version and try again

The reason this happens is because the read DeploymentConfig or update DC in memory or call Update actions can potentially fail due to the DC being updated concurrently by some other component, usually by Kubernetes or OpenShift itself.

Thus it is recommended to avoid the read, update-in-memory, or push-update actions as much as possible. One remedy is to use the Patch operation, for more information see the Resource Operations section. Another remedy would be to retry the operation when the optimistic concurrency error is encountered.

Dependency management

odo uses glide to manage dependencies. glide is not strictly required for building odo but it is required when managing dependencies under the vendor/ directory.

If you want to make changes to dependencies please make sure that glide is installed and is in your $PATH.

Installing glide

  1. Download glide:

    $ go get -u github.com/Masterminds/glide
  2. Check that glide is working

    $ glide --version

Using glide to add a new dependency

Adding a new dependency

  1. Update the glide.yaml file. Add the new package or sub-packages to the glide.yaml file. You can add a whole new package as a dependency or just a few sub-packages.

  2. Run glide update --strip-vendor to get the new dependencies.

  3. Commit the updated glide.yaml, glide.lock and vendor files to git.

Updating dependencies

  1. Set new package version in glide.yaml file.

  2. Run glide update --strip-vendor to update dependencies

Release guide

Releasing a new version

Making artifacts for a new release is automated. When a new git tag is created, the Travis-ci deploy job automatically builds binaries and uploads it to the GitHub release page.

To release a new version:

  1. Create a PR that:

    • Updates the version in the following files:

    • Updates the CLI reference documentation in the docs/cli-reference.md file:

      $ make generate-cli-reference
  2. Merge the above PR.

  3. Once the PR is merged create and push the new git tag for the version.

    $ git tag v0.0.1
    $ git push upstream v0.0.1

    Or create the new release using the GitHub site (this must be a proper release and not a draft).

    Note
    Do not upload any binaries for the release. When the new tag is created, Travis-CI starts a special deploy job. This job builds the binaries automatically (using make prepare-release) and then uploads it to the GitHub release page. When the job finishes you should see the binaries on the GitHub release page. The release is now marked as a draft.
  4. Update the descriptions and publish the release.

  5. Verify that packages have been uploaded to the rpm and deb repositories.

  6. Update the Homebrew package:

    1. Check commit id for the released tag git show-ref v0.0.1

    2. Create a PR to update :tag and :revision in the odo.rb file in kadel/homebrew-odo.

  7. Confirm the binaries are available in the GitHub release page.

  8. Create a PR and update the file build/VERSION with the latest version number.

odo-bot

odo-bot is the GitHub user that provides automation for certain tasks in odo.

It uses the .travis.yml script to upload binaries to the GitHub release page using the deploy-github-release personal access token.

Licenses

odo uses wwhrd to check license compatibility of vendor packages. The configuration for wwhrd is stored in .wwhrd.yml.

The whitelist section is for licenses that are always allowed. The blacklist section is for licenses that are never allowed and will always fail a build. Any licenses that are not explicitly mentioned come under the exceptions secion and need to be explicitly allowed by adding the import path to the exceptions.

More details about the license compatibility check tool can be found here