Harness Deployment API Deep Dive

Harness StartExecution API Deep Dive

tags: training api graphql

Introduction

Customer Use Cases

  • Customers will incorporate the API call in their Jenkins or another system, in order to fire off the deployment (System to System interaction)
  • A Harness CLI tool, that enables developers to deploy via an in house cli that triggers deployment (i.e. devkit harness deploy)
  • The User uses the Harness API Explorer and starts an execution via GraphQL query in a user-login session.

Security

Most enterprise customers will ask about the security around the Harness API, specifically the Write capabilities and the Execution API capabilities.

Common Questions

  • How do we know who triggered the deployment?
  • What access does that user have?
  • Is the triggered deployment logged?
  • Is the triggered deployment viewable in our Audit Trail?

Authentication

  • Our APIs do support Authentication, there are two ways of doing so today
  1. The User Logged In Session, So as your user you can navigate to the API Explorer and execute queries based on your specific user permissions

  1. Via the API Key, the key is scoped to a user group so Harness can confirm who the user executing the API command is via the User Group they are a part of.

Authorization

  • The authorization with the API comes from the API Token.
  • The API Token is scoped to a Harness User Group or it can be scoped to multiple
  • For list apis, the data being returned also depends on the permissions, you are only allowed to see what your User Group is authorized to read and have access to.

Example:

We add an API Key scoped to two different permission groups

Below, the user will see the key with the “Permissions Inherited From” field

The Permissions those groups have will be tied to the token. The user or system that uses the token will only be authorized to perform the specific actions on their behalf.

Since neither of these user groups have permission to execute a production pipeline, when I try to deploy the pipeline with the key:

The API throws an error stating that the user is not authorized to deploy

NOTE:

  • The Authorization and Authentication of the API doesn’t apply to specific portions, the entire API follows this model
  • The API Key, organizations may be worried about the key getting stale, its on the Admins to rotate the key and provide it to the team
    • How do I rotate the key?
      • Delete the old API Key and Create a new one

Examples

Getting Piplines By Name

Sample Query to get Pipeline By Name and the variables associated with it

{
  pipelineByName(applicationId: "iIfYmHDqTj6Gsygtwt4VRQ", pipelineName: "Guestbook Pipeline") {
    id
    name
     pipelineVariables {
      name
      type
      required
    }
  }
}

Sample Response:

{
  "data": {
    "pipelineByName": {
      "id": "YdK1ONo9TnyzIK5-plsnAg",
      "name": "Guestbook Pipeline",
      "pipelineVariables": [
        {
          "name": "skipCV",
          "type": "Text",
          "required": false
        },
        {
          "name": "hotfix",
          "type": "Text",
          "required": false
        },
        {
          "name": "branch",
          "type": "Text",
          "required": false
        }
      ]
    }
  }
}

Getting Workflow By Name

Sample Query to get Workflow By Name and the variables associated with it

{
  workflowByName(applicationId: "iIfYmHDqTj6Gsygtwt4VRQ", workflowName: "K8s Rolling Deployment") {
    id
    name
    workflowVariables {
      name
      type
      required
    }
  }
}

Sample Response:

{
  "data": {
    "workflowByName": {
      "id": "VzViT24gTluKWNFpdrL95A",
      "name": "K8s Rolling Deployment",
      "workflowVariables": [
        {
          "name": "Environment",
          "type": "Environment",
          "required": true
        },
        {
          "name": "Service",
          "type": "Service",
          "required": true
        },
        {
          "name": "InfraDefinition_KUBERNETES",
          "type": "Infrastructure definition",
          "required": true
        },
        {
          "name": "hotfix",
          "type": "Text",
          "required": false
        },
        {
          "name": "branch",
          "type": "Text",
          "required": false
        },
        {
          "name": "skipCV",
          "type": "Text",
          "required": false
        }
      ]
    }
  }
}

Sample Queries to Deploy Fargate Prod Pipeline

This example has no workflow variables, just a plain deployment

So I first queried the by Application Name and Returned Application ID and PipelineID

query {
  applicationByName(name:"Harness Demo"){
    id
    name
    pipelines(limit:4){
      nodes{
        id
        description
      }
    }
  }
}

After getting the correct information, I created the startExecution Mutation to execute the deployment.

I need to pass the Application ID and Pipeline ID as inputs. Based on my mutation, I want to return notes, execution id, and application id.

  startExecution (input:$startExecution){
    clientMutationId
    execution {
      notes
      id
      application{
        id
      }
    }
  }
}

So for the Parameters to execute the query, I passed the information below:

{
  "startExecution": {
    "applicationId": "<YOUR APPLICATION ID>",
    "notes": "Demo",
    "executionType": "PIPELINE",
    "entityId": "<YOUR PIPELINE ID>",
    "serviceInputs": {
      "name": "fargate-nginx",
      "artifactValueInput": {
        "buildNumber": {
          "buildNumber": "1.10.1",
          "artifactSourceName": "library_nginx"
        },
        "valueType": "BUILD_NUMBER"
        }
    }
    }
}

Once, passing in these parameters, I received a return payload and the pipeline executed!

{
  "data": {
    "startExecution": {
      "clientMutationId": null,
      "execution": {
        "notes": "Demo",
        "id": "<YOUR EXECUTION ID>",
        "application": {
          "id": "<YOUR APPLICATION ID>"
        }
      }
    }
  }
}

and I also see in the Continuous Deployment Page this beautiful deployment!

Deploying a Pipeline w/ Variables

My Query:


mutation($startExecution: StartExecutionInput!){
  startExecution (input:$startExecution){
    clientMutationId
    execution {
      notes
      status
      startedAt
      id
      application{
        id
      }
    }
  }
}

My Query Variables Input:

{
	"startExecution": {
		"applicationId": "iIfYmHDqTj6Gsygtwt4VRQ",
		"notes": "Demo",
		"executionType": "PIPELINE",
		"entityId": "YdK1ONo9TnyzIK5-plsnAg",
		"serviceInputs": [
			{
				"name": "guestbook",
				"artifactValueInput": {
					"buildNumber": {
						"buildNumber": "v6",
						"artifactSourceName": "guestbook"
					},
					"valueType": "BUILD_NUMBER"
				}
			}
		],
		"variableInputs": [
			{
				"name": "skipCV",
				"variableValue": {
					"type": "NAME",
					"value": "true"
				}
			},
			{
				"name": "branch",
				"variableValue": {
					"value": "master",
					"type": "NAME"
				}
			},
			{
				"name": "hotfix",
				"variableValue": {
					"value": "false",
					"type": "NAME"
				}
			}
		]
	}
}

Proof that the workflow variables were passed in:

"branch"

"hotfix"

Since it was set to false all stages were deployed

"skipCV"

The end result:

Deploying a Workflow w/ Variables

My Query for the Execution:

mutation($startExecution: StartExecutionInput!){
  startExecution (input:$startExecution){
    clientMutationId
    execution {
      notes
      status
      startedAt
      id
      application{
        id
      }
    }
  }
}

My Query Variables:

{
	"startExecution": {
		"applicationId": "iIfYmHDqTj6Gsygtwt4VRQ",
		"notes": "Demo",
		"executionType": "WORKFLOW",
		"entityId": "VzViT24gTluKWNFpdrL95A",
		"serviceInputs": {
			"name": "guestbook",
			"artifactValueInput": {
				"buildNumber": {
					"buildNumber": "v6",
					"artifactSourceName": "guestbook"
				},
				"valueType": "BUILD_NUMBER"
			}
    },
    "variableInputs": [
      {
      "name": "Environment",
      "variableValue": {
        "type": "NAME",
        "value": "dev"
      }
    },
    {
      "name": "InfraDefinition_KUBERNETES",
      "variableValue": {
       	"type": "NAME",
        "value": "k8s-dev"
      }
    },
      {
      "name":"Service",
        "variableValue": {
          "type": "NAME",
          "value": "guestbook"
        }
    },
      {
      "name": "hotfix",
      "variableValue": {
        "type": "NAME",
        "value": "false"
      },
      "name": "branch",
      "variableValue": {
        "type": "NAME",
        "value": "master"
      },
      "name": "skipCV",
      "variableValue": {
        "type": "NAME",
        "value": "true"
      }
      }
    ]
	}
}

Proof of Execution

The value was “master” for “branch”. The “branch” was successfully passed:

and the Prometheus step was skipped due to the condition. “skipCV” was set to “true”.

Checking the Status of Deployments via API

For Pipeline Execution

query{
  execution(executionId: "QzhqUddfQiKMxJdEzZC3bg"){
    id
    ... on PipelineExecution {
      id
      status
    }
  }
}

For Workflow Execution

{
  execution(executionId:"_Mffj1GZQd-VgNN_XWA7NQ"){
    id
    status
    ... on WorkflowExecution {
      id
      outcomes{
        nodes{
          execution {
            id
            endedAt
            startedAt
          }
        }
      }
    }
  }
}

Scripts for CLI tools or System to System Interaction

For our Scripters, here is a sample script with the startExecution API call:

#ARGUMENTS
APPLICATIONID="<YOUR APPLICATION ID>"
PIPELINEID="<YOUR PIPELINE ID>"
BUILDNO="<YOUR BUILD NO>"
SERVICENAME="<YOUR SERVICE NAME>"
ARTIFACTSOURCENAME="<YOUR ARTIFACT SOURCE>"
# Execute Pipeline
func_execute_pipeline(){
	curl --request POST \
	  --url 'https://app.harness.io/gateway/api/graphql?accountId='$HARNESS_ACCOUNT_ID'' \
	  --header 'content-type: application/json' \
	  --header 'x-api-key:'$HARNESS_KEY' \
	  --data '{"query":"\nmutation($startExecution: StartExecutionInput!){\n  startExecution (input:$startExecution){\n    clientMutationId\n    execution {\n      notes\n      id\n      application{\n        id\n      }\n    }\n  }\n}","variables":{"startExecution":{"applicationId":"'$APPLICATIONID'","notes":"Demo","executionType":"PIPELINE","entityId":"'$PIPELINEID'","serviceInputs":{"name":"'$SERVICENAME'","artifactValueInput":{"buildNumber":{"buildNumber":"'$BUILDNO'","artifactSourceName":"'$ARTIFACTSOURCENAME'"},"valueType":"BUILD_NUMBER"}}}}}'
}

also for the Get Application by Name Script Sample:

# Get Application By Name
APPNAME="<YOUR APP NAME>"
func_getAppByName(){
curl --request POST \
  --url 'https://app.harness.io/gateway/api/graphql?accountId='$HARNESS_ACCOUNT_ID'' \
  --header 'content-type: application/json' \
  --header 'x-api-key:'$HARNESS_KEY' \
  --data '{"query":"query {\n  applicationByName(name:\"'$APPNAME'\"){\n    id\n    name\n    pipelines(limit:4){\n      nodes{\n        id\n        description\n      }\n    }\n  }\n}\n\n"}'
}

You as a user can quickly generate these curl commands, using tools like Insomnia or Postman to generate these calls.

To Download Insomnia: https://insomnia.rest/download/#mac

To Download Postman: https://www.postman.com/downloads/

I used insomnia for most of my work, here is how a user would create these curl commands:

Click on the Arrow key next to your command and select from the drop down:

Select “Copy as Curl” and its automatically copied to your clipboard.

I pasted mine here:

curl --request POST \
  --url 'https://app.harness.io/gateway/api/graphql?accountId=<ACOUNTID> \
  --header 'content-type: application/json' \
  --header 'x-api-key: <API_KEY>' \
  --data '{"query":"\nmutation($startExecution: StartExecutionInput!){\n  startExecution (input:$startExecution){\n    clientMutationId\n    execution {\n      notes\n      status\n      startedAt\n      id\n      application{\n        id\n      }\n    }\n  }\n}","variables":{"startExecution":{"applicationId":"iIfYmHDqTj6Gsygtwt4VRQ","notes":"Demo","executionType":"WORKFLOW","entityId":"VzViT24gTluKWNFpdrL95A","serviceInputs":{"name":"guestbook","artifactValueInput":{"buildNumber":{"buildNumber":"v6","artifactSourceName":"guestbook"},"valueType":"BUILD_NUMBER"}},"variableInputs":[{"name":"Environment","variableValue":{"type":"NAME","value":"dev"}},{"name":"InfraDefinition_KUBERNETES","variableValue":{"type":"NAME","value":"k8s-dev"}},{"name":"Service","variableValue":{"type":"NAME","value":"guestbook"}},{"name":"skipCV","variableValue":{"type":"NAME","value":"true"}}]}}}'
1 Like