-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
function/parse_duration
: Add duration parsing function (#350)
* function/parse_duration: Add duration parsing function * Renamed parse_duration to duration_parse * Added link to Go duration strings in duration_parse function summary and description * Use static assertions in duration_parse tests instead * Added CHANGELOG entry * Removed unused import * Updated time.ParseDuration * Output of make generate
- Loading branch information
1 parent
6911c5d
commit f73bd87
Showing
7 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
kind: FEATURES | ||
body: 'functions/duration_parse: Added a new `duration_parse` function that parses [Go duration strings](https://pkg.go.dev/time#ParseDuration).' | ||
time: 2025-01-23T19:15:29.754845+01:00 | ||
custom: | ||
Issue: "350" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
page_title: "duration_parse function - terraform-provider-time" | ||
subcategory: "" | ||
description: |- | ||
Parse a Go duration string https://pkg.go.dev/time#ParseDuration into an object | ||
--- | ||
|
||
# function: duration_parse | ||
|
||
Given a [Go duration string](https://pkg.go.dev/time#ParseDuration), will parse and return an object representation of that duration. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
# Configuration using provider functions must include required_providers configuration. | ||
terraform { | ||
required_providers { | ||
time = { | ||
source = "hashicorp/time" | ||
# Setting the provider version is a strongly recommended practice | ||
# version = "..." | ||
} | ||
} | ||
# Provider functions require Terraform 1.8 and later. | ||
required_version = ">= 1.8.0" | ||
} | ||
output "example_output" { | ||
value = provider::time::duration_parse("1h") | ||
} | ||
``` | ||
|
||
## Signature | ||
|
||
<!-- signature generated by tfplugindocs --> | ||
```text | ||
duration_parse(duration string) object | ||
``` | ||
|
||
## Arguments | ||
|
||
<!-- arguments generated by tfplugindocs --> | ||
1. `duration` (String) Go time package duration string to parse | ||
|
||
|
||
## Return Type | ||
|
||
The `object` returned from `duration_parse` has the following attributes: | ||
- `hours` (Number) The duration as a floating point number of hours. | ||
- `minutes` (Number) The duration as a floating point number of minutes. | ||
- `seconds` (Number) The duration as a floating point number of seconds. | ||
- `milliseconds` (Number) The duration as an integer number of milliseconds. | ||
- `microseconds` (Number) The duration as an integer number of microseconds. | ||
- `nanoseconds` (Number) The duration as an integer number of nanoseconds. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Configuration using provider functions must include required_providers configuration. | ||
terraform { | ||
required_providers { | ||
time = { | ||
source = "hashicorp/time" | ||
# Setting the provider version is a strongly recommended practice | ||
# version = "..." | ||
} | ||
} | ||
# Provider functions require Terraform 1.8 and later. | ||
required_version = ">= 1.8.0" | ||
} | ||
|
||
output "example_output" { | ||
value = provider::time::duration_parse("1h") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package provider | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/function" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
) | ||
|
||
var durationParseReturnAttrTypes = map[string]attr.Type{ | ||
"hours": types.Float64Type, | ||
"minutes": types.Float64Type, | ||
"seconds": types.Float64Type, | ||
"milliseconds": types.Int64Type, | ||
"microseconds": types.Int64Type, | ||
"nanoseconds": types.Int64Type, | ||
} | ||
|
||
var _ function.Function = &DurationParseFunction{} | ||
|
||
type DurationParseFunction struct{} | ||
|
||
func NewDurationParseFunction() function.Function { | ||
return &DurationParseFunction{} | ||
} | ||
|
||
func (f *DurationParseFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { | ||
resp.Name = "duration_parse" | ||
} | ||
|
||
func (f *DurationParseFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { | ||
resp.Definition = function.Definition{ | ||
Summary: "Parse a [Go duration string](https://pkg.go.dev/time#ParseDuration) into an object", | ||
Description: "Given a [Go duration string](https://pkg.go.dev/time#ParseDuration), will parse and return an object representation of that duration.", | ||
|
||
Parameters: []function.Parameter{ | ||
function.StringParameter{ | ||
Name: "duration", | ||
Description: "Go time package duration string to parse", | ||
}, | ||
}, | ||
Return: function.ObjectReturn{ | ||
AttributeTypes: durationParseReturnAttrTypes, | ||
}, | ||
} | ||
} | ||
|
||
func (f *DurationParseFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { | ||
var input string | ||
|
||
resp.Error = req.Arguments.Get(ctx, &input) | ||
if resp.Error != nil { | ||
return | ||
} | ||
|
||
duration, err := time.ParseDuration(input) | ||
if err != nil { | ||
// Intentionally not including the Go parse error in the return diagnostic, as the message is based on a Go-specific | ||
// reference time that may be unfamiliar to practitioners | ||
tflog.Error(ctx, fmt.Sprintf("failed to parse duration string, underlying time.Duration error: %s", err.Error())) | ||
|
||
resp.Error = function.NewArgumentFuncError(0, fmt.Sprintf("Error parsing duration string: %q is not a valid duration string", input)) | ||
return | ||
} | ||
|
||
durationObj, diags := types.ObjectValue( | ||
durationParseReturnAttrTypes, | ||
map[string]attr.Value{ | ||
"hours": types.Float64Value(duration.Hours()), | ||
"minutes": types.Float64Value(duration.Minutes()), | ||
"seconds": types.Float64Value(duration.Seconds()), | ||
"milliseconds": types.Int64Value(duration.Milliseconds()), | ||
"microseconds": types.Int64Value(duration.Microseconds()), | ||
"nanoseconds": types.Int64Value(duration.Nanoseconds()), | ||
}, | ||
) | ||
|
||
resp.Error = function.FuncErrorFromDiags(ctx, diags) | ||
if resp.Error != nil { | ||
return | ||
} | ||
|
||
resp.Error = resp.Result.Set(ctx, &durationObj) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package provider | ||
|
||
import ( | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/knownvalue" | ||
"github.com/hashicorp/terraform-plugin-testing/plancheck" | ||
"github.com/hashicorp/terraform-plugin-testing/tfversion" | ||
) | ||
|
||
func TestDurationParse_valid(t *testing.T) { | ||
resource.UnitTest(t, resource.TestCase{ | ||
TerraformVersionChecks: []tfversion.TerraformVersionCheck{ | ||
tfversion.SkipBelow(tfversion.Version1_8_0), | ||
}, | ||
ProtoV5ProviderFactories: protoV5ProviderFactories(), | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("1h") | ||
} | ||
`, | ||
ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectKnownOutputValue("test", knownvalue.ObjectExact( | ||
map[string]knownvalue.Check{ | ||
"hours": knownvalue.Float64Exact(1), | ||
"minutes": knownvalue.Float64Exact(60), | ||
"seconds": knownvalue.Float64Exact(3600), | ||
"milliseconds": knownvalue.Int64Exact(3600000), | ||
"microseconds": knownvalue.Int64Exact(3600000000), | ||
"nanoseconds": knownvalue.Int64Exact(3600000000000), | ||
}, | ||
)), | ||
}, | ||
}, | ||
}, | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("60m") | ||
} | ||
`, | ||
ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectEmptyPlan(), | ||
}, | ||
}, | ||
}, | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("3600s") | ||
} | ||
`, | ||
ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectEmptyPlan(), | ||
}, | ||
}, | ||
}, | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("3600000ms") | ||
} | ||
`, | ||
ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectEmptyPlan(), | ||
}, | ||
}, | ||
}, | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("3600000000us") | ||
} | ||
`, | ||
ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectEmptyPlan(), | ||
}, | ||
}, | ||
}, | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("3600000000000ns") | ||
} | ||
`, | ||
ConfigPlanChecks: resource.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectEmptyPlan(), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func TestDurationParse_invalid(t *testing.T) { | ||
resource.UnitTest(t, resource.TestCase{ | ||
TerraformVersionChecks: []tfversion.TerraformVersionCheck{ | ||
tfversion.SkipBelow(tfversion.Version1_8_0), | ||
}, | ||
ProtoV5ProviderFactories: protoV5ProviderFactories(), | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: ` | ||
output "test" { | ||
value = provider::time::duration_parse("abcdef") | ||
} | ||
`, | ||
ExpectError: regexp.MustCompile(`"abcdef" is not a valid duration string.`), | ||
}, | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
--- | ||
page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" | ||
subcategory: "" | ||
description: |- | ||
{{ .Summary | plainmarkdown | trimspace | prefixlines " " }} | ||
--- | ||
|
||
# {{.Type}}: {{.Name}} | ||
|
||
{{ .Description | trimspace }} | ||
|
||
{{ if .HasExample -}} | ||
## Example Usage | ||
|
||
{{tffile .ExampleFile }} | ||
{{- end }} | ||
|
||
## Signature | ||
|
||
{{ .FunctionSignatureMarkdown }} | ||
|
||
## Arguments | ||
|
||
{{ .FunctionArgumentsMarkdown }} | ||
{{ if .HasVariadic -}} | ||
{{ .FunctionVariadicArgumentMarkdown }} | ||
{{- end }} | ||
|
||
## Return Type | ||
|
||
The `object` returned from `duration_parse` has the following attributes: | ||
- `hours` (Number) The duration as a floating point number of hours. | ||
- `minutes` (Number) The duration as a floating point number of minutes. | ||
- `seconds` (Number) The duration as a floating point number of seconds. | ||
- `milliseconds` (Number) The duration as an integer number of milliseconds. | ||
- `microseconds` (Number) The duration as an integer number of microseconds. | ||
- `nanoseconds` (Number) The duration as an integer number of nanoseconds. |
f73bd87
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 🚀 🚀