Skip to content

Create Release

This section describes how you can create releases within Octopus when using the dynamic package name approach and when you have multiple packages within a single deployment. As mentioned in the overview section, DataStar deployments have more than one package as you have to deploy the release tools, provide templates and deploy the packaged scripts.

Manual Deployment

It is possible to create the releases manually when using the dynamic package name approach, however it requires you to set the overall version and the package version with different values.

Firstly when you create a manual release you need to "Expand All" so you can see the versions for all the packages. If we were deploying our user story package from our library feed (for example DAT-243432.76.nupkg) then we would set the Version to be 243432.76 and the dynamic package version to be 76 - this is shown below:

Automated Release Creation

Whilst the manual release creation is useful for the reversal releases (on the basis that reverting changes should be for exceptional circumstances), it is useful to automate the creation of the deployment releases. Typically this will be done in your CI pipeline once you have created a build.

We have created a Python script which you can use to create a release during your build pipelines, it uses the Octopus REST API so you can adapt this for other languages if you wish. This will perform the following steps:

  1. It looks up the Octopus Space Id given an Octopus Space Name, as this is required to look up project by name.
  2. It looks up the Octopus Project Id within the Octopus Space given an Octopus Project Name.
  3. It looks up the Octopus Channel Id within the Octopus Project given an Octopus Channel Name.
  4. For each of the packages referenced it will look up the latest package and set that version, with the exception of dynamic package names which will be set to use the version specified in the arguments.
  5. Finally it will create a release with the multiple package versions resolved.
import json
import requests
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-b', '--base', type=str, required=True)
parser.add_argument('-a', '--apikey', type=str, required=True)
parser.add_argument('-w', '--workitem', type=int, required=True)
parser.add_argument('-v', '--version', type=int, required=True)
parser.add_argument('-c', '--channel', type=str, required=False)
parser.add_argument('-p', '--project', type=str, required=True)
parser.add_argument('-s', '--space', type=str, required=True)
args = parser.parse_args()

spaceName = args.space
projectName = args.project

channelName = "Default"
if args.channel:
    channelName = args.channel
workItem = str(args.workitem)
version = str(args.version)
packageVersion = workItem + '.' + version

session = requests.Session()
session.headers.update({'X-Octopus-ApiKey': str(args.apikey)})

response = session.get(str(args.base) + '/spaces/all')
if response.status_code != 200:
    print('Error response ' + str(response.status_code) + ' looking up space ' + args.story)
    raise SystemExit(1)

spaceId = None
spaces = json.loads(response.content)
for item in spaces:
    if item['Name'] == spaceName:
        spaceId = item['Id']
        break

if spaceId is None:
    print('Error response failed to locate spaceId for name ' + spaceName)
    raise SystemExit(1)

response = session.get(str(args.base) + "/" + spaceId + '/projects/all')
if response.status_code != 200:
    print('Error response ' + str(response.status_code) + ' looking up project ' + projectName)
    raise SystemExit(1)

projectId = None
projects = json.loads(response.content)
for item in projects:
    if item['Name'] == projectName:
        projectId = item['Id']
        break

if projectId is None:
    print('Error response failed to locate projectId for name ' + projectName)
    raise SystemExit(1)

response = session.get(str(args.base) + '/projects/' + projectId + "/channels")
if response.status_code != 200:
    print('Error response ' + str(response.status_code) + ' looking up channels')
    raise SystemExit(1)

channelId = None
channels = json.loads(response.content)
for item in channels['Items']:
    if item['Name'] == channelName:
        channelId = item['Id']
        break

if channelId is None:
    print('Error response failed to locate channelId for name ' + channelName)
    raise SystemExit(1)

response = session.get(str(args.base) + '/'
                       + spaceId + '/deploymentprocesses/deploymentprocess-'
                       + projectId + '/template?channel=' + channelId)

# Create the release body
releaseBody = json.loads('{{ "ChannelId": "{0}", "ProjectId": "{1}", "Version": "{2}", "SelectedPackages": [] }}'.format(channelId, projectId, packageVersion))

templates = json.loads(response.content)
for item in templates['Packages']:
    response = session.get(str(args.base) + '/' + spaceId + '/feeds/'
                           + item['FeedId'] + '/packages/versions?packageId='
                           + item['PackageId'] + '&take=1')
    versionSpec = packageVersion
    versionInfo = json.loads(response.content)
    for versionItem in versionInfo['Items']:
        versionSpec = versionItem['Version']
        break
    if versionSpec == packageVersion:
        versionSpec = version

    releaseBody['SelectedPackages'] \
        .append(json.loads('{{ "ActionName": "{0}", "PackageReferenceName": "{1}", "Version": "{2}" }}'
                .format(item['ActionName'], item['PackageReferenceName'], versionSpec)))

print('Creating Release:' + json.dumps(releaseBody))
result = session.post(str(args.base) + '/' + spaceId
                      + '/releases?ignoreChannelRules=false', data=json.dumps(releaseBody))

if result.status_code != 200 | result.status_code != 201:
    print('Error response ' + str(result.status_code) + ' message: ' + str(result.content))
    raise SystemExit(1)