This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

v1alpha1 specs

templates.kluctl.io/v1alpha1 documentation

1 - ObjectTemplate

ObjectTemplate documentation

The ObjectTemplate API defines templates that are rendered based on a matrix of input values.

Example

apiVersion: v1
kind: ConfigMap
metadata:
  name: input-configmap
  namespace: default
data:
  x: someValue
---
apiVersion: templates.kluctl.io/v1alpha1
kind: ObjectTemplate
metadata:
  name: example-template
  namespace: default
spec:
  serviceAccountName: example-template-service-account
  prune: true
  matrix:
    - name: input1
      object:
        ref:
          apiVersion: v1
          kind: ConfigMap
          name: input-configmap
  templates:
    - object:
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: "templated-configmap"
        data:
          y: "{{ matrix.input1.x }}"
    - raw: |
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: "templated-configmap-from-raw"
        data:
          z: "{{ matrix.input1.x }}"        

The above manifests show a simple example that will create two ConfigMaps from one input ConfigMap. The individual fields possible in ObjectTemplate are described further down.

Spec fields

The following fields are supported in spec.

serviceAccountName

ObjectTemplate requires a service account to access cluster objects. This is required when it gathers input objects for the matrix and when it applies rendered objects. Please see security for some important notes!

For this to work, the referenced service account must have at least GET, CREATE and UPDATE permissions for the involved objects and kinds. For the above example, the following service account would be enough:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: example-template-service-account
  namespace: default
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-template-service-account
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-template-service-account
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: example-template-service-account
subjects:
  - kind: ServiceAccount
    name: example-template-service-account
    namespace: default

interval

Specifies the interval at which the ObjectTemplate is reconciled.

suspend

If set to true, reconciliation is suspended.

prune

If true, the Template Controller will delete rendered objects when either the ObjectTemplate gets deleted or when the rendered object disappears from the rendered objects list.

matrix

The matrix defines a list of matrix entries, which are then used as inputs into the templates. Each entry results in a list of values associated with the entry name. All lists are then multiplied together to form the actual matrix of input values.

Each matrix entry has a name, which is later used to identify the value in the template.

As an example, if you have two entries with simple lists with the following values:

matrix:
- name: input1
  list:
    - a: v1
      b: v2
- name: input2
  list:
    - c: v3
      d: v4

It will lead to the following matrix:

- input1:
    a: v1
    b: v2
  input2:
    c: v3
    d: v4

Now take the following matrix example with an entry with two list items:

matrix:
- name: input1
  list:
    - a: v1
      b: v2
    - a: v1_2
      b: v2_2
- name: input2
  list:
    - c: v3
      d: v4

It will lead to the following matrix:

- input1:
    a: v1
    b: v2
  input2:
    c: v3
    d: v4
- input1:
    a: v1_2
    b: v2_2
  input2:
    c: v3
    d: v4

Each input value is then used as input when rendering the templates. In the above examples, it means that all templates are rendered twice, once with matrix.input1 set to the first input value and the second time with the second input value.

The following matrix entry types are supported:

list

This is the simplest form and represents a list of arbitrary objects. See the above examples.

Due to the use of controller-gen and an internal limitation in regard to validation and CRD generation, list elements must be objects at the moment. A future version of the Template Controller will support arbitrary values (e.g. numbers and strings) as elements.

object

This refers an object on the cluster. The object is read by the controller and then used as an input value for the matrix. Example:

matrix:
- name: input1
  object:
    ref:
      apiVersion: v1
      kind: ConfigMap
      name: input-configmap

The referenced object can be of any kind, but the used service account must have access to the referenced object. The read object is then wholly used as matrix input.

To only use a sub-part of the referenced object, set jsonPath to a valid JSON Path pointing to the subfield(s) that you want to use. Example:

matrix:
- name: input1
  object:
    ref:
      apiVersion: v1
      kind: ConfigMap
      name: input-configmap
      jsonPath: .data

This will make the data field available as input instead of the full object, meaning that values can be used inside the templates by simply referring {{ matrix.input1.my_key }} (no .data required).

In case you want to interpret a subfield as an input list instead of a single value, set expandLists to true. Example:

matrix:
- name: input1
  object:
    ref:
      apiVersion: templates.kluctl.io/v1alpha1
      kind: ListGithubPullRequests
      name: list-gh-prs
      jsonPath: status.pullRequests
      expandLists: true

This will lead to one matrix input per list element at status.pullRequests instead of a single matrix input that represents the list.

templates

templates is a list of template objects. Each template object is rendered and applied once per entry from the multiplied matrix inputs. When rendering, the context contains the global variable matrix representing the current entry. matrix has one member field per named matrix input.

In the lists example from above, this would for example give matrix.input1 and matrix.input2 for each render invocation.

In case a template object is missing the namespace, it is set to the namespace of the ObjectTemplate object.

The service account used for the ObjectTemplate must have permissions to get and apply the resulting objects.

There are currently two forms of template objects supported, object and raw. object is an inline object where each string field is treated as independent template to render. raw represents one large (multi-line) string that is rendered in one-go and then unmarshalled as yaml/json.

It is recommended to prefer object over raw and only revert to raw templates when you need to perform advanced templating (e.g. {% if ... %} or other control structures) or when it is important to treat a field as non-string (e.g. boolean or number) when unmarshalled into an object. An example for such case would be if you want to use a template value for replicas of a Deployment, which MUST be a number.

Example for an object:

templates:
- object:
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: "templated-configmap"
    data:
      y: "{{ matrix.input1.x }}"

Example for a raw template object:

templates:
- raw: |
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: "templated-configmap-from-raw"
    data:
      z: "{{ matrix.input1.x }}"    

See templating for more details on the templating engine.

2 - GitProjector

GitProjector documentation

The GitProjector API defines projections of Git repositories.

Projection of Git repositories means that the content of selected branches and selected files are loaded into Kubernetes, accessible through the status of the GitProjector.

The projected branches and files can then be used as matrix inputs for an ObjectTemplate.

Example

apiVersion: templates.kluctl.io/v1alpha1
kind: GitProjector
metadata:
  name: preview
  namespace: default
spec:
  interval: 1m
  url: https://github.com/kluctl/kluctl-examples.git
  # In case you use a private repository
  secretRef:
    name: git-credentials
  ref:
    branch: main
  files:
    - glob: "preview-envs/preview-*.yaml"
      parseYaml: true

The above example creates a GitProjector that will periodically clone the kluctl-examples repo, look for the main branch and all files matching the given glob. It will then parse all yamls and make them available through the GitProjector’s status:

apiVersion: templates.kluctl.io/v1alpha1
kind: GitProjector
metadata:
  name: preview
  namespace: default
spec:
  ...
status:
  allRefsHash: 104d3dc9b5ffabf5ba3c76532fb71da58757c494acdcb7dff3665d256f516612
  conditions:
  - lastTransitionTime: "2022-12-14T09:09:51Z"
    message: Success
    observedGeneration: 1
    reason: Success
    status: "True"
    type: Ready
  result:
  - files:
    - parsed:
      - envName: preview-env1
        replicas: 3
      path: preview-envs/preview-env1.yaml
    - parsed:
      - envName: preview-env2
        replicas: 1
      path: preview-envs/preview-env2.yaml
    ref:
      branch: main

Spec fields

The following fields are supported in spec.

interval

Specifies the interval at which the GitProjector is reconciled.

suspend

If set to true, reconciliation is suspended.

url

The git url of the repository to project. Can either be a https or a git/ssh url.

ref

The git reference to project. Either spec.ref.branch or spec.ref.tag must be set.

Both tags and refs can be regular expressions. In case of a regular expression, the controller will include all matching refs in the status.result field.

secretRef

Same as in the Kluctl Controllers KluctlDeployment

files

List of file to project into the status. Must be of the format:

...
spec:
  ...
  files:
    - glob: "my-file.yaml"
      parseYaml: true

Each entry must at least contain a glob which is used to match files. The controller uses the https://github.com/gobwas/glob library for pattern matching.

If parseYaml is set to true, the controller will try to parse matching files as yaml and include the parsed structured data in the resulting status. Parsing of yaml is done with the assumption that all files possibly contain multiple yaml documents, meaning that even yaml files with just a single document will result in a parsed list of one document.

Consider the following matching yaml file:

envName: preview-env1
replicas: 3

This will result in the following projection:

...
status:
  result:
  - files:
    - parsed:
      - envName: preview-env1
        replicas: 3
      path: preview-envs/preview-env1.yaml
    ref:
      branch: main

If parseYaml is false, the result will contain a raw string representation of the matching files:

...
status:
  result:
  - files:
    - path: preview-envs/preview-env1.yaml
      raw: |-
        envName: preview-env1
        replicas: 3        
    ref:
      branch: main

3 - GithubComment

GithubComment documentation

The GithubComment API allows to post a comment to a GitHub Pull Request.

Example

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
  namespace: default
data:
  my-key: |
    This can by **any** form of [Markdown](https://en.wikipedia.org/wiki/Markdown) supported by Github.    
---
apiVersion: templates.kluctl.io/v1alpha1
kind: GithubComment
metadata:
  name: comment-gh
  namespace: default
spec:
  github:
    owner: my-org-or-user
    repo: my-repo
    pullRequestId: 1234
    tokenRef:
      secretName: git-credentials
      key: github-token
  comment:
    source:
      configMap:
        name: my-configmap
        key: my-key

The above example will post a comment to the specified pull request. The comment’s content is loaded from the ConfigMap my-configmap. Other sources are also supported, see the source field documentation for details.

The comment will be updated whenever the underlying comment source changes.

Spec fields

suspend

If set to true, reconciliation of this comment is suspended.

github

Specifies which GitHub project and pull request to post the comment to.

github.owner

Specifies the user or organisation name where the repository is localed.

github.repo

Specifies the repository name to query PRs for.

github.tokenRef

In case of private repositories, this field can be used to specify a secret that contains a GitHub API token.

github.pullRequestId

Specifies the ID of the pull request.

comment

This field specifies the necessary information for the comment content.

comment.id

This optional field specifies the identifier to mark the comment with so that the controller can identify it. It defaults to a generated id built from the namespace and name of the comment resource.

comment.source

This specifies the comment source. Multiple source types are supported, specified via a sub-field.

comment.source.text

Raw text for the template’s content. Example:

apiVersion: templates.kluctl.io/v1alpha1
kind: GithubComment
metadata:
  name: comment-gh
  namespace: default
spec:
  github:
    owner: my-org-or-user
    repo: my-repo
    pullRequestId: 1234
    tokenRef:
      secretName: git-credentials
      key: github-token
  comment:
    source:
      text: |
        This can by **any** form of [Markdown](https://en.wikipedia.org/wiki/Markdown) supported by Github.        
comment.source.configMap

Uses a ConfigMap as source for the comment’s content. Example:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
  namespace: default
data:
  my-key: |
    This can by **any** form of [Markdown](https://en.wikipedia.org/wiki/Markdown) supported by Github.    
---
apiVersion: templates.kluctl.io/v1alpha1
kind: GithubComment
metadata:
  name: comment-gh
  namespace: default
spec:
  github:
    owner: my-org-or-user
    repo: my-repo
    pullRequestId: 1234
    tokenRef:
      secretName: git-credentials
      key: github-token
  comment:
    source:
      configMap:
        name: my-configmap
        key: my-key
comment.source.textTemplate

Uses a TextTemplate as source for the comment’s content. Example:

apiVersion: templates.kluctl.io/v1alpha1
kind: TextTemplate
metadata:
  name: my-texttemplate
  namespace: default
spec:
  inputs:
    ... # See TextTemplate documentation for details.
  template: |
    This can by **any** form of [Markdown](https://en.wikipedia.org/wiki/Markdown) supported by Github.    
---
apiVersion: templates.kluctl.io/v1alpha1
kind: GithubComment
metadata:
  name: comment-gh
  namespace: default
spec:
  github:
    owner: my-org-or-user
    repo: my-repo
    pullRequestId: 1234
    tokenRef:
      secretName: git-credentials
      key: github-token
  comment:
    source:
      textTemplate:
        name: my-texttemplate

4 - GitlabComment

GitlabComment documentation

The GitlabComment API allows to post a comment to a Gitlab Merge Request.

Example

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
  namespace: default
data:
  my-key: |
    This can by **any** form of [Markdown](https://en.wikipedia.org/wiki/Markdown) supported by Gitlab.    
---
apiVersion: templates.kluctl.io/v1alpha1
kind: GitlabComment
metadata:
  name: comment-gl
  namespace: default
spec:
  gitlab:
    project: my-group/my-repo
    mergeRequestId: 1234
    tokenRef:
      secretName: git-credentials
      key: gitlab-token
  comment:
    source:
      configMap:
        name: my-configmap
        key: my-key

The above example will post a comment to the specified pull request. The comment’s content is loaded from the ConfigMap my-configmap. Other sources are also supported, see the source field documentation for details.

The comment will be updated whenever the underlying comment source changes.

Spec fields

suspend

If set to true, reconciliation of this comment is suspended.

gitlab

Specifies which Gitlab project and merge request to post the comment to.

gitlab.project

Specifies the user or organisation name where the repository is localed.

gitlab.repo

Specifies the repository name to query PRs for.

gitlab.tokenRef

In case of private repositories, this field can be used to specify a secret that contains a Gitlab API token.

github.pullRequestId

Specifies the ID of the pull request.

comment

Same as in GithubComment

5 - ListGithubPullRequests

ListGithubPullRequests documentation

The ListGithubPullRequests API allows to query the GitHub API for a list of pull requests (PRs). These PRs can be filtered when needed. The resulting list of PRs is written into the status of the ListGithubPullRequests object.

The resulting PRs list inside the status can for example be used in ObjectTemplate to create objects based on pull requests.

Example

apiVersion: templates.kluctl.io/v1alpha1
kind: ListGithubPullRequests
metadata:
  name: list-gh-prs
  namespace: default
spec:
  interval: 1m
  owner: podtato-head
  repo: podtato-head
  state: open
  base: main
  head: podtato-head:.*
  tokenRef:
    secretName: git-credentials
    key: github-token

The above example will regularly (1m interval) query the GitHub API for PRs inside the podtato-head repository. It will filter for open PRs and for PRs against the main branch.

Spec fields

interval

Specifies the interval in which to query the GitHub API. Defaults to 5m.

owner

Specifies the user or organisation name where the repository is localed.

repo

Specifies the repository name to query PRs for.

tokenRef

In case of private repositories, this field can be used to specify a secret that contains a GitHub API token.

Specifies the head to filter PRs for. The format must be user:ref-name / organization:ref-name. The head field can also contain regular expressions.

base

Specifies the base branch to filter PRs for. The base field can also contain regular expressions.

labels

Specifies a list of labels to filter PRs for.

state

Specifies the PR state to filter for. Can either be open, closed or all. Default to all.

limit

Limits the number of results to accept. This is a safeguard for repositories with hundreds/thousands of PRs. It defaults to 100.

Resulting status

The query result is written into the status.pullRequests field of the ListGithubPullRequests object. Each entry represents a reduced version of the GitHub Pulls API results. The result is reduced in verbosity to avoid overloading the Kubernetes apiserver. Reduction means that all fields containing user, repo, orga and label fields are reduced to id, name, login, owner and full_name.

Please note that the resulting PR objects do not follow the typical camel case notion found in CRDs, as these represent a copy of GitHub API objects.

Example:

apiVersion: templates.kluctl.io/v1alpha1
kind: ListGithubPullRequests
metadata:
  name: list-gh-prs
  namespace: default
spec:
  ...
status:
  conditions:
  - lastTransitionTime: "2022-11-07T14:55:36Z"
    message: Success
    observedGeneration: 3
    reason: Success
    status: "True"
    type: Ready
  pullRequests:
  - base:
      label: podtato-head:main
      ref: main
      repo:
        full_name: podtato-head/podtato-head
        name: podtato-head
      sha: de7e66af16d41b0ef83de9a0b3be6f5cf0caf942
    body: "..."
    created_at: "2022-02-02T23:06:28Z"
    head:
      label: vivek:issue-79_implement_ms_ketch
      ref: issue-79_implement_ms_ketch
      repo:
        full_name: vivek/podtato-head
        name: podtato-head
      sha: 6379b4c8f413dae70daa03a5a13de4267486fd59
    number: 151
    state: open
    title: '...'
    updated_at: "2022-02-04T03:53:03Z"

6 - TextTemplate

TextTemplate documentation

GithubComment

The TextTemplate API allows to define text templates that are rendered into the status of the TextTemplate. The result can for example be used in GitlabComment/GithubComment.

Example

For the below example to work, you will also have to deploy the RBAC resources documented in ObjectTemplate.

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
  namespace: default
data:
  mykey: input-value
---
apiVersion: templates.kluctl.io/v1alpha1
kind: TextTemplate
metadata:
  name: example
  namespace: default
spec:
  serviceAccountName: example-template-service-account
  inputs:
    - name: input1
      object:
        ref:
          apiVersion: v1
          kind: ConfigMap
          name: my-configmap
  template: |
    This template text can use variables from the inputs defined above, for example this: {{ inputs.input1.data.mykey }}.    

The above example will render the given template text and write it into the result of the object:

apiVersion: templates.kluctl.io/v1alpha1
kind: TextTemplate
...
status:
  conditions:
  - lastTransitionTime: "2023-01-16T11:24:15Z"
    message: Success
    observedGeneration: 2
    reason: Success
    status: "True"
    type: Ready
  result: 'This template text can use variables from the inputs defined above, for example this: input-value.'

Spec fields

suspend

If set to true, reconciliation of this TextTemplate is suspended.

serviceAccountName

The service account to use while retrieving template inputs. See the ObjectTemplate documentation for details.

inputs

List of template inputs which are then available while rendering the text template. At the moment, only Kubernetes objects are supported as inputs, but other types of inputs might be supported in the future.

Example:

apiVersion: templates.kluctl.io/v1alpha1
kind: TextTemplate
metadata:
  name: example
  namespace: default
spec:
  serviceAccountName: example-template-service-account
  inputs:
    - name: input1
      object:
        ref:
          apiVersion: v1
          kind: ConfigMap
          name: my-configmap
          namespace: default
        jsonPath: data
  template: |
    This template text can use variables from the inputs defined above, for example this: {{ inputs.input1.mykey }}.    

inputs.name

Specifies the name of the input, which is then used to refer to the input inside the text template.

inputs.object

Specifies the object to load as input. The specified service account must have proper permissions to access this object.

template

Specifies the raw template text to be rendered in the reconciliation loop. While rendering, each input is available via the global inputs variable and the specified name of the input, e.g. `{{ inputs.my_input.sub_field }}.

See templating for more details on the templating engine.

templateRef

Specifies another object to load the template text from. Currently only ConfigMaps are supported.

templateRef.configMap:

Specifies a ConfigMap to load the template from.

Example:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
  namespace: default
data:
  mykey: input-value
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-template
  namespace: default
data:
  template: |
    This template text can use variables from the inputs defined above, for example this: {{ inputs.input1.data.mykey }}.    
---
apiVersion: templates.kluctl.io/v1alpha1
kind: TextTemplate
metadata:
  name: example
  namespace: default
spec:
  serviceAccountName: example-template-service-account
  inputs:
    - name: input1
      object:
        ref:
          apiVersion: v1
          kind: ConfigMap
          name: my-configmap
  templateRef:
    configMap:
      name: my-template
      key: template

Resulting status

The resulting rendered template is written into the status and can then be used by other objects, e.g. GitlabComment/GithubComment.

Example:

...
status:
  conditions:
    - lastTransitionTime: "2023-01-16T11:24:15Z"
      message: Success
      observedGeneration: 3
      reason: Success
      status: "True"
      type: Ready
  result: 'This template text can use variables from the inputs defined above,
    for example this: input-value.'

7 - ListGitlabMergeRequests

ListGitlabMergeRequests documentation

The ListGitlabMergeRequests API allows to query the Gitlab API for a list of merge requests (MRs). These MRs can be filtered when needed. The resulting list of MRs is written into the status of the ListGitlabMergeRequests object.

The resulting MRs list inside the status can for example be used in ObjectTemplate to create objects based on pull requests.

Example

apiVersion: templates.kluctl.io/v1alpha1
kind: ListGitlabMergeRequests
metadata:
  name: list-gl-mrs
  namespace: default
spec:
  interval: 1m
  project: my-group/my-repo
  state: opened
  targetBranch: main
  sourceBranch: prefix-.*
  tokenRef:
    secretName: git-credentials
    key: gitlab-token

The above example will regularly (1m interval) query the Gitlab API for MRs inside the my-group/my-repo project. It will filter for open MRs and for MRs against the main branch.

Spec fields

interval

Specifies the interval in which to query the GitHub API. Defaults to 5m.

project

Specifies the Gitlab project to query MRs for. Must be in the format group/project, where group can also contain subgroups (e.g. group1/group2/project).

tokenRef

In case of private repositories, this field can be used to specify a secret that contains a Gitlab API token.

targetBranch

Specifies the target branch to filter MRs for. The targetBranch field can also contain regular expressions.

sourceBranch

Specifies the source branch to filter MRs for. The sourceBranch field can also contain regular expressions.

labels

Specifies a list of labels to filter MRs for.

state

Specifies the PR state to filter for. Can either be opened, closed, locked, merged or all. Default to all.

limit

Limits the number of results to accept. This is a safeguard for repositories with hundreds/thousands of MRs. It defaults to 100.

Resulting status

The query result is written into the status.mergeRequests field of the ListGitlabMergeRequests object. The list is identical to what is documented in the Gitlab Merge requests API.

Please note that the resulting PR objects do not follow the typical camel case notion found in CRDs, as these represent a copy of Gitlab API objects.

Example:

apiVersion: templates.kluctl.io/v1alpha1
kind: ListGitlabMergeRequests
metadata:
  name: list-gl-mrs
  namespace: default
spec:
  ...
status:
  conditions:
  - lastTransitionTime: "2022-11-07T14:55:36Z"
    message: Success
    observedGeneration: 3
    reason: Success
    status: "True"
    type: Ready
  mergeRequests:
  - id: 1
    iid: 1
    project_id: 3
    title: test1
    description: fixed login page css paddings
    state: merged
    merged_by:
      id: 87854
      name: Douwe Maan
      username: DouweM
      state: active
      avatar_url: 'https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png'
      web_url: 'https://gitlab.com/DouweM'
    merge_user:
      id: 87854
      name: Douwe Maan
      username: DouweM
      state: active
      avatar_url: 'https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png'
      web_url: 'https://gitlab.com/DouweM'
    merged_at: '2018-09-07T11:16:17.520Z'
    closed_by: null
    closed_at: null
    created_at: '2017-04-29T08:46:00Z'
    updated_at: '2017-04-29T08:46:00Z'
    target_branch: master
    source_branch: test1
    upvotes: 0
    downvotes: 0
    author:
      id: 1
      name: Administrator
      username: admin
      state: active
      avatar_url: null
      web_url: 'https://gitlab.example.com/admin'
    assignee:
      id: 1
      name: Administrator
      username: admin
      state: active
      avatar_url: null
      web_url: 'https://gitlab.example.com/admin'
    assignees:
      - name: Miss Monserrate Beier
        username: axel.block
        id: 12
        state: active
        avatar_url: >-
          http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon          
        web_url: 'https://gitlab.example.com/axel.block'
    reviewers:
      - id: 2
        name: Sam Bauch
        username: kenyatta_oconnell
        state: active
        avatar_url: >-
          https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon          
        web_url: 'http://gitlab.example.com//kenyatta_oconnell'
    source_project_id: 2
    target_project_id: 3
    labels:
      - Community contribution
      - Manage
    draft: false
    work_in_progress: false
    milestone:
      id: 5
      iid: 1
      project_id: 3
      title: v2.0
      description: Assumenda aut placeat expedita exercitationem labore sunt enim earum.
      state: closed
      created_at: '2015-02-02T19:49:26.013Z'
      updated_at: '2015-02-02T19:49:26.013Z'
      due_date: '2018-09-22'
      start_date: '2018-08-08'
      web_url: 'https://gitlab.example.com/my-group/my-project/milestones/1'
    merge_when_pipeline_succeeds: true
    merge_status: can_be_merged
    detailed_merge_status: not_open
    sha: '8888888888888888888888888888888888888888'
    merge_commit_sha: null
    squash_commit_sha: null
    user_notes_count: 1
    discussion_locked: null
    should_remove_source_branch: true
    force_remove_source_branch: false
    allow_collaboration: false
    allow_maintainer_to_push: false
    web_url: 'http://gitlab.example.com/my-group/my-project/merge_requests/1'
    references:
      short: '!1'
      relative: my-group/my-project!1
      full: my-group/my-project!1
    time_stats:
      time_estimate: 0
      total_time_spent: 0
      human_time_estimate: null
      human_total_time_spent: null
    squash: false
    task_completion_status:
      count: 0
      completed_count: 0