Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 131 additions & 1 deletion services/ssm.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,136 @@ component {
return apiCall( requestSettings, 'GetParameters', payload );
}

/**
* Get parameters by path.
* https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParametersByPath.html
* @Path The hierarchy for the parameter. Hierarchies start with a forward slash (/).
* @Recursive Retrieve all parameters within a hierarchy.
* @WithDecryption Return decrypted secure string value. Return decrypted values for secure string parameters.
* @MaxResults The maximum number of items to return.
* @NextToken The token for the next set of items to return. (For pagination.)
*/
public any function getParametersByPath(
required string Path,
boolean Recursive,
boolean WithDecryption,
numeric MaxResults,
string NextToken
) {
var requestSettings = api.resolveRequestSettings( argumentCollection = arguments );
var payload = { 'Path': arguments.Path };
if ( !isNull( arguments.Recursive ) ) {
payload[ 'Recursive' ] = arguments.Recursive;
}
if ( !isNull( arguments.WithDecryption ) ) {
payload[ 'WithDecryption' ] = arguments.WithDecryption;
}
if ( !isNull( arguments.MaxResults ) ) {
payload[ 'MaxResults' ] = arguments.MaxResults;
}
if ( !isNull( arguments.NextToken ) ) {
payload[ 'NextToken' ] = arguments.NextToken;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the four null checks above be combined into a for loop? e.g.:

for (
    var key in [
        'Recursive',
        'WithDecryption',
        'MaxResults',
        'NextToken'
    ]
) {
    if ( structKeyExists( arguments, key ) ) {
        payload[ key ] = arguments[ key ];
    }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I just code in the same style and patterns I see in the library. Line 168 of personalize.cfc or Line 235 in connect.cfc for instance. Can i blame Eric Peterson? :)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point 😁 I haven't been consistent about asking for this. But I do prefer the style above.

return apiCall( requestSettings, 'GetParametersByPath', payload );
}

/**
* Retrieves all parameters from a specified path.
* Handles pagination to ensure all parameters within the path are fetched.
* @Path The hierarchy path to query parameters. Must start with a forward slash (e.g., "/production").
* @Recursive If true, retrieves parameters from the entire hierarchy under the specified path.
* @withDecryption If true, retrieves decrypted values for secure string parameters.
*/
public array function getAllParametersByPath(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about adding this method. I don't think there are any other places in the code base where there is a method that calls the API multiple times (potentially) before returning. I know other libraries do add iterators, so maybe there is a place for this, but it is not something that has been done before. I would have expected the iteration to be implemented in your own calling code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its your decision. Remove or not? I typically create a helper function to handle common repetitive tasks, especially when reusing the same library across multiple projects. SSM's 10 parameter limit is quite low by pagination standards.

My use case is that I store application configurations by path in the parameter store and always end up with more than 10 i need to retrieve.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I understand the low parameter limit is annoying, but for now I'd like to leave helper functions like this, that don't actually call the API themselves, out of the library.

required string path,
required boolean recursive,
required boolean withDecryption
) {
var allParameters = [];
var nextToken = "";
var parametersResponse = {};

// Initial loop condition
var continueLoop = true;

while (continueLoop) {
// Make the API call to get parameters by path
parametersResponse = getParametersByPath(
Path = arguments.path,
Recursive = arguments.recursive,
WithDecryption = arguments.withDecryption,
MaxResults = 10, // Maximum items per call
NextToken = nextToken
);

// Append the retrieved parameters to the results array, merging arrays into a flat array
if (structKeyExists(parametersResponse.data, "Parameters")) {
arrayAppend(allParameters, parametersResponse.data.Parameters, true);
}

// Update nextToken and determine if the loop should continue
if (structKeyExists(parametersResponse.data, "NextToken")) {
nextToken = parametersResponse.data.NextToken;
} else {
continueLoop = false;
}
}

return allParameters;
}

/**
* Add or update a parameter in the Parameter Store.
* https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_PutParameter.html
* @Name The name of the parameter to create or update.
* @Value The parameter value.
* @Type The type of parameter. Valid values are String, StringList, and SecureString.
* @Overwrite Overwrite an existing parameter of the same name.
*/
public any function putParameter(
required string Name,
required string Value,
required string Type,
boolean Overwrite
) {
var requestSettings = api.resolveRequestSettings( argumentCollection = arguments );
var payload = {
'Name': arguments.Name,
'Value': (len(arguments.Value) ? arguments.Value : " "),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the idea here with the overwrite of the value argument when it is an empty string?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSM doesn't allow empty values. They must have a length greater than or equal to 1.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather leave this choice up to the calling code - whatever that passes in as the value should be passed on to API. I don't want to change the value argument like this.

'Type': arguments.Type
};
if ( !isNull( arguments.Overwrite ) ) {
payload[ 'Overwrite' ] = arguments.Overwrite;
}
return apiCall( requestSettings, 'PutParameter', payload );
}

/**
* Delete a parameter from the Parameter Store.
* https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_DeleteParameter.html
* @Name The name of the parameter to delete.
*/
public any function deleteParameter(
required string Name
) {
var requestSettings = api.resolveRequestSettings( argumentCollection = arguments );
var payload = { 'Name': arguments.Name };
return apiCall( requestSettings, 'DeleteParameter', payload );
}

/**
* Delete multiple parameters from the Parameter Store.
* https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_DeleteParameters.html
* @Names The names of the parameters to delete.
*/
public any function deleteParameters(
required array Names
) {
var requestSettings = api.resolveRequestSettings( argumentCollection = arguments );
var payload = { 'Names': arguments.Names };
return apiCall( requestSettings, 'DeleteParameters', payload );
}

public string function getHost(
required string region
) {
Expand Down Expand Up @@ -81,4 +211,4 @@ component {
return apiResponse;
}

}
}