Adapter example
This tutorial is a step-by-step instruction on building an example adapter using the CWM SDK. It gives an idea on the adapter structure and on how you provide input to define adapter activities to be consumed by a workflow worker. Before you start, you need to go through the Prerequisites section to set up your development environment.
Important
Running any cwm-sdk command for the first time (except for create-adapter and cwm-sdk version) will trigger the SDK to build a docker image and deploy the adapter builder container. This initial setup may take some time, but once the image is built, subsequent commands will execute much faster.
Step 1: Create a new adapter
In a terminal window, open a command-line terminal and run:
cwm-sdk create-adapter -vendor vendor1 -product product1 -feature feature1
Now you have a new catalog named vendor1.product1 at your home dierctory with the following contents:
Makefile
adapter.properties
docs
go
lib
proto
./docs:
index.html
./go:
common
go.mod
feature1
./go/common:
errors.go
logger.go
./go/feature1:
./proto:
vendor1.product1.common.adapter.proto
vendor1.product1.feature1.adapter.proto
Step 2: Define mock activity
The CWM SDK has generated the .proto files. In the vendor1.product1.feature1.adapter.proto file, define the interface of the adapter:
-
Open the
vendor1.product1.feature1.adapter.protofile with a text editor or inside a terminal window. The contents are as below.syntax = "proto3"; package vendor1.product1.feature1; option go_package = "cisco.com/cwm/adapters/vendor1/product1/feature1"; import "google/protobuf/struct.proto"; service Activities { // CWM SDK NOTE: Activity functions are defined as RPCs here e.g. /* Documentation for MyActivity */ rpc MyActivity(MyRequest) returns (MyResponse); } // CWM SDK NOTE: Messages here e.g. /* Documentation for MyRequest */ message MyRequest { string stringInput = 1; int32 integerInput = 2; bool booleanInput = 3; google.protobuf.Value anyInput = 4; // CWM SDK NOTE: Useful for accepting a json object from the workflow definition } /* Documentation for MyResponse */ message MyResponse { string stringOutput = 1; int32 integerOutput = 2; bool booleanOutput = 3; google.protobuf.Value anyOutput = 4; // CWM SDK NOTE: Useful for returning a json object to the workflow definition } -
To define your activity, replace the placeholder 'MyActivity' with a mock 'Hello' activity, along with the MyRequest and MyResponse placeholder names and message parameters as shown below:
service Activities { /* Documentation for Hello Activity */ rpc Hello(MyRequest) returns (MyResponse); } /* Documentation for MyRequest */ message MyRequest { string name = 1; } /* Documentation for MyResponse */ message MyResponse { string message = 1; }
Step 3: Generate adapter source code
- Based on the
adapter.protofile that you have edited and on the remaining.protofiles, generate the source go code for the adapter and inspect the files. In the main adapter directory, run:
cwm-sdk update-adapter && ls
The output will look like:
.go/
common
go.mod
feature1
go//common:
errors.go
logger.go
vendor1.product1.common.adapter.pb.go
go//feature1:
activities.go
adapter.go
vendor1.product1.feature1.adapter.pb.go
-
The
.adapter.pb.gofiles generated using the Protobufs compiler define all the messages from theadapter.protofiles.
Caution
The .adapter.pb.gofiles should not be edited manually.
-
The generated
activities.gofile contains stubs for all the RPCs you have defined in the.adapter.protofile. Open the file:``` package feature1
import ( "cisco.com/cwm/adapters/vendor1/product1/common" "context" )
func (adp Adapter) Hello(ctx context.Context, req MyRequest, cfg common.Config) (MyResponse, error) {
var res *MyResponse var err error // CWM SDK NOTE: Implement your activity logic here... return res, err} ```
-
Edit the file to return a message:
func (adp *Adapter) Hello(ctx context.Context, req *MyRequest, cfg *Config) (*MyResponse, error) { return &MyResponse {Message: "Hello, " + req.GetName() + "!"}, nil }
Define another activity
If you wish to add another activity to the existing feature set (go package):
-
Open and edit the
adapter.protofile and define another activity underneath the existing one:service Activities { rpc Hello(MyRequest) returns (MyResponse); rpc Fancy(MyRequest) returns (MyResponse); } -
Update the activities go code using the SDK:
``` cwm-sdk extend-adapter -activity fancy -feature feature1
```
After you update the fancy activity part of the
.adapter.protofile with a sample logic, update the adapter:cwm-sdk update-adapterOnce the code is generated, the
activities.gofile is updated with the new 'Fancy' activity stub, while the code for the 'Hello' activity remains.
Step 4: Add another feature
If you wish to add another feature (go package) to the example adapter, use the extend-adapter command. In the main adapter directory, run:
cwm-sdk extend-adapter -feature feature2
-
A new
vendor1.product1.feature2.adapter.protofile has been added for your adapter:.proto/ vendor1.product1.common.adapter.proto vendor1.product1.feature2.adapter.proto vendor1.product1.feature1.adapter.proto -
To define activities for the new feature, open the
vendor1.product1.feature2.adapter.protofile, and modify the contents accordingly:syntax = "proto3"; package vendor1.product1.feature2; option go_package = "cisco.com/cwm/adapters/vendor1/product1/feature2"; import "google/protobuf/struct.proto"; service Activities { /* Documentation for Goodbye Activity */ rpc Goodbye(MyRequest) returns (MyResponse); } /* Documentation for MyRequest */ message MyRequest { string name = 1; } /* Documentation for MyResponse */ message MyResponse { string message = 1; } -
Generate the code for the 'feature2' package and activities.
cwm-sdk update-adapter -features feature2.go/goodbyes activities.go adapter.go vendor1.product1.feature2.adapter.pb.go
Step 5: Create an installable archive
cwm-sdk create-installable
The generated tar.gz archive contains the all required files of the adapter and can be installed in CWM. The go vendor command has been executed in order to eliminate any external dependencies.
Create custom secrets
This guide describes how to implement a custom secret in a Cisco CWM adapter. Custom secrets allow adapter developers to define and consume provider-specific authentication or sensitive configuration data beyond the built-in secret types (Basic Auth, Bearer Token, and generic Token).
Implement an adapter with custom secret
Create adapter skeleton
Generate a new adapter with custom secret support enabled:
cwm-sdk create-adapter \
-vendor <vendor> \
-product <product> \
-feature <feature> \
-custom-secret
This command creates the adapter project structure, enables custom secret handling, and generates default .proto and .go files for secrets and resources.
Define custom secret in the .proto
Edit the adapter .proto definition:
proto/<vendor>.<product>.common.adapter.proto
Replace the generic Secret message with your own structured definition, for example:
message Secret {
message ApiToken {
string api_key = 1;
string region = 2;
}
ApiToken apiToken = 1;
}
Notes:
- The secret schema is adapter-specific
- You can define multiple fields and nested messages
Generate go code from the updated .proto
Regenerate adapter code:
cwm-sdk update-adapter
This command generates go structs for the custom secret, updates secret.go and related files, and keeps SDK and adapter code in sync with the proto definition
Implement the secret interface
Edit the following file:
go/common/secret.go
Implement GetCustom() to return the custom secret fields:
func (s *Secret) GetCustom() interface{} {
return s.GetApiToken()
}
Ensure the built-in secret getters return nil:
func (s *Secret) GetBasicAuth() *secret.BasicAuth { return nil }
func (s *Secret) GetToken() *secret.Token { return nil }
func (s *Secret) GetBearer() *secret.Bearer { return nil }
This will ensure that the adapter uses only the custom secret.
Implement authentication logic
Edit:
go/common/resource.go
Update GetAuthentication() to handle your custom secret type, for instance:
func (r *Resource) GetAuthentication() (map[string]string, error) {
if custom := r.Secret.GetCustom(); custom != nil {
token := custom.(*common.Secret_ApiToken)
return map[string]string{
"Authorization": "ApiKey " + token.ApiKey,
}, nil
}
return nil, nil
}
Best practices:
- Use
logger.Mute(true)when handling secrets, so that secret values are never logged - Construct headers, tokens, or request data as required by the target system
Build the adapter installable
Create the adapter installable artifact:
cwm-sdk create-installable
Now the adapter package will include the custom secret definition, and can be deployed and configured in CWM.
Adapter signing
Cisco CWM supports digitally signed adapters to ensure authenticity, integrity, and security compliance. Adapter signing allows CWM to verify that an adapter was produced by a trusted developer and has not been modified after it was built. This feature is backward compatible, so unsigned adapters continue to work.
How adapter signing works
-
Developer signs the adapter package using a private key,
-
Signed adapter is uploaded to the CWM platform,
-
CWM verifies the signature using the developer’s registered public key.
Note
Signature verification happens automatically during adapter upload and is logged in the API service logs.
How to sign an adapter
Step 1: Generate signing keys
Use OpenSSL to generate an RSA key pair:
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:4096
openssl pkey -in private.pem -pubout -out public.pem
Key requirements: - Algorithm: RSA
- Format: PKCS#8
- Encoding: PEM
- Key size: Minimum 2048 bits
Step 2: Define the author
Add the author name to adapter.properties. The value must exactly match the registered author name (case-sensitive):
author=developer-name
Step 3: Build and sign the adapter
Create the installable using the private key:
cwm-sdk create-installable -sign-key private.pem
This generates a signed installable file (.tar.gz.signed) that contains: - The original unsigned adapter archive
- A PKCS#1 signature file
Step 4: Register the public key with CWM
Provide the public.pem file and the author name in the CWM UI Workflow Administration → Public Keys.
Feedback