Developer guide

Contributing

There are large parts of the Kubernetes API that are not covered by this library yet, but we welcome anyone who wishes to help us make it more complete.

If you want to contribute, read the rest of this guide, open an issue to discuss the changes you want to do (if you feel it is needed), and finally send us a Pull Request with your changes.

We strive to have good test coverage, and PRs with failing or ignored tests will not be accepted. We also encourage you to add at least a minimal set of tests for the new code you write. Since some parts of the library are harder to test than others, look at other, similar, parts of the library to see the level of testing wanted.

Some tests in the codebase uses vcrpy, but we are migrating away from those because of the difficulties in updating them when the code changes. If you find you are modifying code that is tested using vcrpy, the best thing would be if you rewrite the tests. If that is not an option, try to manually adjust the vcrpy “cassettes” to make the test work. Failing that, mark the test skipped, and mention this in the PR.

We use Prospector for code quality/style checking, and PRs failing this check might be required to fix any issues before being merged. The code style in the project is close to PEP8, so should not present any big problems.

Adding support for new object types

If you want to create support for a new type of object in the Kubernetes API, the best thing to start with is the Kubernetes API reference documentation. The first thing you need to do, is find the “top most” type, ie. the type you operate on through the API. We can call this an API type. Examples are Pod, Service or Ingress.

Create a new module under k8s.models if your type doesn’t belong in any of the existing ones, then start by creating a class inheriting from Model named the same as the type is named in the Kubernetes documentation. Use the same casing as the Kubernetes documentation uses.

For your API type, inside the class you should declare an inner class called Meta, which has a single field url_template. This should be the URL template used when getting, creating or updating objects of this type (see the above mentioned examples).

For each field in the Kubernetes documentation, add a field to the class named exactly the same (including case). If the name is an invalid python identifier, add a _ suffix (so exec becomes exec_). The value of the field should be an instance of a subclass of Field, depending on the semantics of the field.

Use this class… …when
ListField the field is a list (aka array)
OnceField the field can only be set on new objects
RequiredField the field is required
ReadOnlyField the field is set by the API server
Field none of the above applies

The Field class takes three parameters:

type
The type of value this field contains. Can be simple types (int, bool etc), datetime.datetime or subclasses of Model.
default_value
The field is set to this value when an instance of the class is created. The default default is None.
alt_type

The Kubernetes API will in some cases accept two types for a field (usually integer and string). This is the less common of the two, otherwise it has the same meaning as type.

This parameter is not available for the ListField, but so far we have not come across any case where it is needed.

Once you have created a class for your API type, some of the fields will refer to new types which have yet to be defined. Make sure to use existing types defined elsewhere (possibly moving them to k8s.models.common if they are used in multiple places. Continue defining new subclasses of Model for each type needed, until you have created all the types required for your API type to be completely specified.

Note

If the Kubernetes documentation says the type is object, the python type should be dict. If the Kubernetes documentation says the type is string, we use six.text_type to maintain compatibility with both Python 2 and 3. Most other simple types are obvious.

How the HorizontalPodAutoscaler type is implemented
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class CrossVersionObjectReference(Model):
    kind = RequiredField(six.text_type)
    name = RequiredField(six.text_type)
    apiVersion = Field(six.text_type)


class HorizontalPodAutoscalerSpec(Model):
    scaleTargetRef = RequiredField(CrossVersionObjectReference)
    minReplicas = Field(int, 2)
    maxReplicas = RequiredField(int)
    targetCPUUtilizationPercentage = Field(int, 50)


class HorizontalPodAutoscaler(Model):
    class Meta:
        url_template = "/apis/autoscaling/v1/namespaces/{namespace}/horizontalpodautoscalers/{name}"

    metadata = Field(ObjectMeta)
    spec = Field(HorizontalPodAutoscalerSpec)

Releasing a new version

To make a new release there are a couple steps to follow. Ideally, we want to release from master, as often as possible. Version numbers should adhere to SemVer. When you have a passing build that you want to make a release from, do the following steps:

  • Create an annotated tag for the commit in question, naming it v<major>.<minor>.<bugfix>. For instance:

    $ git tag -a v0.0.2 a1b2c3d4
    
  • Push the new tag to github:

    $ git push origin v0.0.2
    
  • Go to the build page on SemaphoreCI and click the “Deploy manually” button (you must be logged in, and have access to the project).

  • Select the “Make release” checkbox and continue

  • A new release with the version you selected as a tag should now be uploaded to PyPI and Github