A year ago I wrote an ABX action to give me features that were not available in the early integration of vRA/SaltStack Config. Since then the vRA Cloud Template schema for the Cloud.Saltstack resource type has been extended with many good capabilities, but it is still limited for some features needed in Enterprise deployments, namely:
- Create Salt grains as part of the vRA blueprint – used to define resource-specific key/value pairs – e.g. Server Role, Application Name
- Execute a highstate for the deployed resource – which typically references the above grains – e.g. apply web-server.sls if the grain named app has a value of web-server
- Store and reference SLS files in a remote git repository
The last one is important to me as currently the Cloud.Saltstack property stateFiles can only reference SLS files seen by the Salt RaaS. This one took a while to figure out and with the help of VMware GSS I was informed that even though the Salt Master can see the files on a remote gitfs fileserver (visible by running the command salt-run fileserver.file_list saltenv=base on the master), only files visible by RaaS can be referenced in the vRA Cloud.Saltstack resource type. You can create/modify/view these files in the SaltStack Config GUI at Config / File Server
Why is it important? I want to use code version control for my state files and store binary objects (e.g. software to deploy) in the file server and the SaltStack Config GUI is not appropriate for this.
Solution
The three requirements above can be achieved by writing two new state files and storing them on the RaaS File Server. No ABX action is needed and it leverages the native capabilities of vRA/SaltStack Config so should be fully supported. In addition, it solves a number of limitations from the previous ABX action, namely:
- Is multi-cloud ready – does not use Invoke-VMScript which requires VMware Tools
- As the Salt Minion is deployed by the Salt Master to the deployed machine, there is no need to pre-install the Minion in the template and hence you can use a native image from your cloud provider (and don’t have to maintain custom images)
- Fully managed by the vRA deployment lifecycle/Event Topics – i.e. previously the deployment would show as Completed in vRA even though the ABX Action was still executing.
- The minion key is automatically accepted and there is no need to use a custom autosign_grains value and modify the master accordingly. This is a much more secure method of key acceptance
- The minion key is automatically removed from the Salt master when the deployment is deleted (native feature of the vRA/SaltStack Config integration).
Note: the solution below is only a workaround until vRA/SSC can natively achieve the requirements. Of course, I can’t say if/when these capabilities will be rolled into a future product release but I’m hopeful!
This solution has been developed and tested with vRA/SaltStack Config version 8.9.1
Pre-requisites
- vRA/SSC deployed and operational
- Cloud templates created
- Image remote access credentials with system-level privileges
- A highstate file (top.sls) created and tested using salt-call state.highstate on a minion
How it Works
- A vRA Cloud Template is created which includes the Cloud.Saltstack resource

- The Cloud.Saltstack resource is configured to execute two new state files: /setgrain/init.sls and /high-state/init.sls

- The Cloud.Saltstack resource is configured with variables that define the desired grain key/value pairs
- Upon deployment, the Salt deploy.minion job is executed, the minion key is accepted, then the state.apply job is executed using two new SLS files.
- /setgrain/init.sls creates grains on the minion using the key/values in the blueprint variables
- /high-state/init.sls applies a highstate. The state files referenced by the top.sls can (and should) be on your remote Git file server
Deployment
Create the State Files on the RaaS File Server
- Log in to SaltStack Config and navigate to Config / File Server
- Create a new file under the base environment for the high-state state

- Create a new file under the base environment for the setgrain state. Note: There are 5 grains defined in this state file – if you use less in the vRA blueprint it is OK, but if you want more grains you will need to add them here.

Create the vRA Cloud Template
In vRA, select the Cloud.SaltStack resource and define the grains and state files. The example code below uses properties in the Cloud.Machine resource for the grains – this is because I will be using these values in other areas e.g. creating VM tags, custom names etc
Note 1: the blueprint example has a create_dns property which is used as part of an ABX subscription as defined here. It is optional for the purposes of this article.
Note 2: I am using cloud-init for the initial image customisation, but you could of course use a VMware Guest Customization spec if only deploying to a vSphere environment.
Note 3: If you are interested in the syntax of the username property for remoteAccess, take a look at the article on ternary logic here
name: SSC grains and highstate
formatVersion: 1
version: 1
#------------------------ Inputs ------------------------#
inputs:
tenant:
type: string
title: Tenant Designation
description: Select a three character designation e.g. T03
default: T98
maxLength: 3
minLength: 3
pattern: ([A-Z])\d\d
operatingSystem:
type: string
oneOf:
- title: CentOS 7
const: centos-79-cloud-init
- title: CentOS 8
const: centos-83-cloud-init
- title: Debian Linux 11
const: debian-11-cloud-init
- title: Ubuntu Server 20.04 LTS
const: ubuntu-server-2004-cloud-init
- title: Ubuntu Server 21.10
const: ubuntu-server-2110-cloud-init
- title: Windows Server 2019
const: windows-server-2019-cloud-init
title: Operating System and Version
description: Select an Operationg System
default: centos-79-cloud-init
cnameRecord:
type: string
title: DNS CNAME record
description: Enter the name of the CNAME record to create that will point to the generated hostname.
default: ''
resources:
#------------------------- SaltStack -------------------------#
Cloud_SaltStack_1:
type: Cloud.SaltStack
properties:
hosts:
- ${resource.Cloud_Machine_1.id}
masterId: saltstack_enterprise_installer
additionalMinionParams:
log_level: debug
saltEnvironment: base
stateFiles:
- /setgrain/init.sls
- /high-state/init.sls
variables:
key1: environment
value1: ${resource.Cloud_Machine_1.environment}
key2: appName
value2: ${resource.Cloud_Machine_1.appName}
key3: tenant
value3: ${input.tenant}
key4: serverRole
value4: ${resource.Cloud_Machine_1.serverRole}
#-------------------------- Machine -------------------------#
Cloud_Machine_1:
type: Cloud.Machine
properties:
image: ${input.operatingSystem}
folderName: Tenants\${input.tenant}
flavor: small
constraints:
- tag: cloud:private
cnameRecord: ${input.cnameRecord}
remoteAccess:
authentication: usernamePassword
username: '${input.operatingSystem == "windows-server-2019-cloud-init" ? "Administrator" : "root"}'
password: ${secret.tmpl_admin_password}
cloudConfig: |
#cloud-config
write_files:
- content: |
hostname: ${self.resourceName}
Cloud: ${resource.Cloud_Machine_1.account}
set_timezone: Asia/Dubai
set_hostname: ${self.resourceName}
networks:
- network: ${resource.Cloud_Network_1.id}
assignment: static
#------------------------ SaltStack ------------------------#
environment: Dev
appName: My App
tenant: T98
serverRole: Web Server
#-------------------- ABX Action Control --------------------#
create_dns: true
#--------------------------- Networks -----------------------#
Cloud_Network_1:
type: Cloud.Network
properties:
networkType: existing
constraints:
- tag: cloud:private
Execution Result
After deployment has completed, you can check the history and view the status of each provisioning phase and view the high-level logs returned by SaltStack Config job execution.

Within SaltStack Config GUI, navigate to Activity / Completed where you can view the deployment tasks. Selecting the JID link will show more details of each job execution

When opening the link for the state.apply job then selecting the HIGHSTATE tab, you can review the states applied to the minion. Under Changes: > ret: you will see the details of the tasks executed by the top.sls

Conclusion
The above workarounds offer a much better and streamlined way of using SaltStack Config with vRA deployments. We can now leverage enterprise best practices for state file code version control and have removed some of the limitations allowing us to truly leverage SaltStack Config within multi-cloud deployments. Kudos go to VMware GSS and Engineering with their support and assistance to bring this all together.
Appendix
It is possible to execute the salt highstate in the same SLS file after the grains are set, but I wanted to split them out into two separate state files to ease troubleshooting, and also because if/when VMware include these enhancements in the core products, they may not arrive at the same time so it makes sense to differentiate these tasks into separate actions.
If you wanted to merge the two state functions, you could create and reference a single SLS in the blueprint with the following code:
/setgrain/init.sls
{% set key_1 = salt.pillar.get('key1','') %}
{% set value_1 = salt.pillar.get('value1','') %}
{% set key_2 = salt.pillar.get('key2','') %}
{% set value_2 = salt.pillar.get('value2','') %}
{% set key_3 = salt.pillar.get('key3','') %}
{% set value_3 = salt.pillar.get('value3','') %}
{% set custom_grains =({key_1:value_1,key_2:value_2,key_3:value_3 }) %}
{% for key, value in custom_grains.items() %}
{% if key|length %}
append_grains_{{key}}:
grains.present:
- name: {{key}}
- value: {{value}}
{% endif %}
{% endfor %}
high:
module.run:
- name: state.apply
- require:
{% for key, value in custom_grains.items() %}
{% if key|length %}
- append_grains_{{key}}
{% endif %}
{% endfor %}