Skip to content

Commit ea5918c

Browse files
authored
Merge pull request #44445 from hashicorp/f-transcribe-start-transcription-job
New action: `aws_transcribe_start_transcription_job`
2 parents f3d9be6 + 54b65ff commit ea5918c

13 files changed

+699
-1
lines changed

.changelog/44445.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-action
2+
aws_transcribe_start_transcription_job
3+
```

internal/service/transcribe/service_package_gen.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package transcribe
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"time"
11+
12+
"github.com/aws/aws-sdk-go-v2/aws"
13+
"github.com/aws/aws-sdk-go-v2/service/transcribe"
14+
awstypes "github.com/aws/aws-sdk-go-v2/service/transcribe/types"
15+
"github.com/hashicorp/terraform-plugin-framework/action"
16+
"github.com/hashicorp/terraform-plugin-framework/action/schema"
17+
"github.com/hashicorp/terraform-plugin-framework/types"
18+
"github.com/hashicorp/terraform-plugin-log/tflog"
19+
"github.com/hashicorp/terraform-provider-aws/internal/actionwait"
20+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
21+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
22+
"github.com/hashicorp/terraform-provider-aws/names"
23+
)
24+
25+
const (
26+
transcriptionJobPollInterval = 5 * time.Second
27+
transcriptionJobProgressInterval = 30 * time.Second
28+
)
29+
30+
// @Action(aws_transcribe_start_transcription_job, name="Start Transcription Job")
31+
func newStartTranscriptionJobAction(_ context.Context) (action.ActionWithConfigure, error) {
32+
return &startTranscriptionJobAction{}, nil
33+
}
34+
35+
var (
36+
_ action.Action = (*startTranscriptionJobAction)(nil)
37+
)
38+
39+
type startTranscriptionJobAction struct {
40+
framework.ActionWithModel[startTranscriptionJobActionModel]
41+
}
42+
43+
type startTranscriptionJobActionModel struct {
44+
framework.WithRegionModel
45+
TranscriptionJobName types.String `tfsdk:"transcription_job_name"`
46+
MediaFileUri types.String `tfsdk:"media_file_uri"`
47+
LanguageCode fwtypes.StringEnum[awstypes.LanguageCode] `tfsdk:"language_code"`
48+
IdentifyLanguage types.Bool `tfsdk:"identify_language"`
49+
IdentifyMultipleLanguages types.Bool `tfsdk:"identify_multiple_languages"`
50+
MediaFormat fwtypes.StringEnum[awstypes.MediaFormat] `tfsdk:"media_format"`
51+
MediaSampleRateHertz types.Int64 `tfsdk:"media_sample_rate_hertz"`
52+
OutputBucketName types.String `tfsdk:"output_bucket_name"`
53+
OutputKey types.String `tfsdk:"output_key"`
54+
Timeout types.Int64 `tfsdk:"timeout"`
55+
}
56+
57+
func (a *startTranscriptionJobAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
58+
resp.Schema = schema.Schema{
59+
Description: "Starts an Amazon Transcribe transcription job to transcribe audio from a media file. The media file must be uploaded to an Amazon S3 bucket before starting the transcription job.",
60+
Attributes: map[string]schema.Attribute{
61+
"transcription_job_name": schema.StringAttribute{
62+
Description: "A unique name for the transcription job within your AWS account.",
63+
Required: true,
64+
},
65+
"media_file_uri": schema.StringAttribute{
66+
Description: "The Amazon S3 location of the media file to transcribe (e.g., s3://bucket-name/file.mp3).",
67+
Required: true,
68+
},
69+
names.AttrLanguageCode: schema.StringAttribute{
70+
CustomType: fwtypes.StringEnumType[awstypes.LanguageCode](),
71+
Description: "The language code for the language used in the input media file. Required if identify_language and identify_multiple_languages are both false.",
72+
Optional: true,
73+
},
74+
"identify_language": schema.BoolAttribute{
75+
Description: "Enable automatic language identification for single-language media files. Cannot be used with identify_multiple_languages.",
76+
Optional: true,
77+
},
78+
"identify_multiple_languages": schema.BoolAttribute{
79+
Description: "Enable automatic language identification for multi-language media files. Cannot be used with identify_language.",
80+
Optional: true,
81+
},
82+
"media_format": schema.StringAttribute{
83+
CustomType: fwtypes.StringEnumType[awstypes.MediaFormat](),
84+
Description: "The format of the input media file. If not specified, Amazon Transcribe will attempt to determine the format automatically.",
85+
Optional: true,
86+
},
87+
"media_sample_rate_hertz": schema.Int64Attribute{
88+
Description: "The sample rate of the input media file in Hertz. If not specified, Amazon Transcribe will attempt to determine the sample rate automatically.",
89+
Optional: true,
90+
},
91+
"output_bucket_name": schema.StringAttribute{
92+
Description: "The name of the Amazon S3 bucket where you want your transcription output stored. If not specified, output is stored in a service-managed bucket.",
93+
Optional: true,
94+
},
95+
"output_key": schema.StringAttribute{
96+
Description: "The Amazon S3 object key for your transcription output. If not specified, a default key is generated.",
97+
Optional: true,
98+
},
99+
names.AttrTimeout: schema.Int64Attribute{
100+
Description: "Maximum time in seconds to wait for the transcription job to start. Defaults to 300 seconds (5 minutes).",
101+
Optional: true,
102+
},
103+
},
104+
}
105+
}
106+
107+
func (a *startTranscriptionJobAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
108+
var config startTranscriptionJobActionModel
109+
110+
// Parse configuration
111+
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
112+
if resp.Diagnostics.HasError() {
113+
return
114+
}
115+
116+
// Get AWS client
117+
conn := a.Meta().TranscribeClient(ctx)
118+
119+
transcriptionJobName := config.TranscriptionJobName.ValueString()
120+
mediaFileUri := config.MediaFileUri.ValueString()
121+
122+
// Set default timeout
123+
timeout := 5 * time.Minute
124+
if !config.Timeout.IsNull() {
125+
timeout = time.Duration(config.Timeout.ValueInt64()) * time.Second
126+
}
127+
128+
tflog.Info(ctx, "Starting transcription job action", map[string]any{
129+
"transcription_job_name": transcriptionJobName,
130+
"media_file_uri": mediaFileUri,
131+
"timeout_seconds": int64(timeout.Seconds()),
132+
})
133+
134+
// Send initial progress update
135+
resp.SendProgress(action.InvokeProgressEvent{
136+
Message: fmt.Sprintf("Starting transcription job %s...", transcriptionJobName),
137+
})
138+
139+
// Build the start transcription job input
140+
input := &transcribe.StartTranscriptionJobInput{
141+
TranscriptionJobName: aws.String(transcriptionJobName),
142+
Media: &awstypes.Media{
143+
MediaFileUri: aws.String(mediaFileUri),
144+
},
145+
}
146+
147+
// Validate language configuration - exactly one must be specified
148+
languageOptions := []bool{
149+
!config.LanguageCode.IsNull() && !config.LanguageCode.IsUnknown(),
150+
!config.IdentifyLanguage.IsNull() && config.IdentifyLanguage.ValueBool(),
151+
!config.IdentifyMultipleLanguages.IsNull() && config.IdentifyMultipleLanguages.ValueBool(),
152+
}
153+
154+
activeCount := 0
155+
for _, active := range languageOptions {
156+
if active {
157+
activeCount++
158+
}
159+
}
160+
161+
switch activeCount {
162+
case 0:
163+
resp.Diagnostics.AddError(
164+
"Missing Language Configuration",
165+
"You must specify exactly one of: language_code, identify_language, or identify_multiple_languages",
166+
)
167+
return
168+
case 1:
169+
// Valid - continue
170+
default:
171+
resp.Diagnostics.AddError(
172+
"Conflicting Language Configuration",
173+
"You can only specify one of: language_code, identify_language, or identify_multiple_languages",
174+
)
175+
return
176+
}
177+
178+
// Set language configuration
179+
if languageOptions[0] {
180+
input.LanguageCode = config.LanguageCode.ValueEnum()
181+
}
182+
if languageOptions[1] {
183+
input.IdentifyLanguage = aws.Bool(true)
184+
}
185+
if languageOptions[2] {
186+
input.IdentifyMultipleLanguages = aws.Bool(true)
187+
}
188+
189+
// Set optional parameters
190+
if !config.MediaFormat.IsNull() && !config.MediaFormat.IsUnknown() {
191+
input.MediaFormat = config.MediaFormat.ValueEnum()
192+
}
193+
194+
if !config.MediaSampleRateHertz.IsNull() {
195+
input.MediaSampleRateHertz = aws.Int32(int32(config.MediaSampleRateHertz.ValueInt64()))
196+
}
197+
198+
if !config.OutputBucketName.IsNull() {
199+
input.OutputBucketName = config.OutputBucketName.ValueStringPointer()
200+
}
201+
202+
if !config.OutputKey.IsNull() {
203+
input.OutputKey = config.OutputKey.ValueStringPointer()
204+
}
205+
206+
// Start the transcription job
207+
_, err := conn.StartTranscriptionJob(ctx, input)
208+
if err != nil {
209+
resp.Diagnostics.AddError(
210+
"Failed to Start Transcription Job",
211+
fmt.Sprintf("Could not start transcription job %s: %s", transcriptionJobName, err),
212+
)
213+
return
214+
}
215+
216+
// Wait for job to move beyond QUEUED: treat IN_PROGRESS or COMPLETED as success, FAILED as failure, QUEUED transitional.
217+
fr, err := actionwait.WaitForStatus(ctx, func(ctx context.Context) (actionwait.FetchResult[*awstypes.TranscriptionJob], error) {
218+
input := transcribe.GetTranscriptionJobInput{TranscriptionJobName: aws.String(transcriptionJobName)}
219+
getOutput, gerr := conn.GetTranscriptionJob(ctx, &input)
220+
if gerr != nil {
221+
return actionwait.FetchResult[*awstypes.TranscriptionJob]{}, fmt.Errorf("get transcription job: %w", gerr)
222+
}
223+
if getOutput.TranscriptionJob == nil {
224+
return actionwait.FetchResult[*awstypes.TranscriptionJob]{}, fmt.Errorf("transcription job %s not found", transcriptionJobName)
225+
}
226+
status := getOutput.TranscriptionJob.TranscriptionJobStatus
227+
return actionwait.FetchResult[*awstypes.TranscriptionJob]{Status: actionwait.Status(status), Value: getOutput.TranscriptionJob}, nil
228+
}, actionwait.Options[*awstypes.TranscriptionJob]{
229+
Timeout: timeout,
230+
Interval: actionwait.FixedInterval(transcriptionJobPollInterval),
231+
ProgressInterval: transcriptionJobProgressInterval,
232+
SuccessStates: []actionwait.Status{
233+
actionwait.Status(awstypes.TranscriptionJobStatusInProgress),
234+
actionwait.Status(awstypes.TranscriptionJobStatusCompleted),
235+
},
236+
TransitionalStates: []actionwait.Status{
237+
actionwait.Status(awstypes.TranscriptionJobStatusQueued),
238+
},
239+
FailureStates: []actionwait.Status{
240+
actionwait.Status(awstypes.TranscriptionJobStatusFailed),
241+
},
242+
ProgressSink: func(fr actionwait.FetchResult[any], meta actionwait.ProgressMeta) {
243+
resp.SendProgress(action.InvokeProgressEvent{Message: fmt.Sprintf("Transcription job %s is currently %s", transcriptionJobName, fr.Status)})
244+
},
245+
})
246+
if err != nil {
247+
var timeoutErr *actionwait.TimeoutError
248+
var failureErr *actionwait.FailureStateError
249+
var unexpectedErr *actionwait.UnexpectedStateError
250+
251+
if errors.As(err, &timeoutErr) {
252+
resp.Diagnostics.AddError(
253+
"Timeout Waiting for Transcription Job",
254+
fmt.Sprintf("Transcription job %s did not reach a running state within %v", transcriptionJobName, timeout),
255+
)
256+
} else if errors.As(err, &failureErr) {
257+
resp.Diagnostics.AddError(
258+
"Transcription Job Failed",
259+
fmt.Sprintf("Transcription job %s failed: %s", transcriptionJobName, failureErr.Status),
260+
)
261+
} else if errors.As(err, &unexpectedErr) {
262+
resp.Diagnostics.AddError(
263+
"Unexpected Transcription Job Status",
264+
fmt.Sprintf("Transcription job %s entered unexpected status: %s", transcriptionJobName, unexpectedErr.Status),
265+
)
266+
} else {
267+
resp.Diagnostics.AddError(
268+
"Error Waiting for Transcription Job",
269+
fmt.Sprintf("Error while waiting for transcription job %s: %s", transcriptionJobName, err),
270+
)
271+
}
272+
return
273+
}
274+
275+
resp.SendProgress(action.InvokeProgressEvent{Message: fmt.Sprintf("Transcription job %s started successfully and is %s", transcriptionJobName, fr.Status)})
276+
logFields := map[string]any{
277+
"transcription_job_name": transcriptionJobName,
278+
"job_status": fr.Status,
279+
}
280+
if fr.Value != nil {
281+
logFields[names.AttrCreationTime] = fr.Value.CreationTime
282+
}
283+
tflog.Info(ctx, "Transcription job started successfully", logFields)
284+
}

0 commit comments

Comments
 (0)