From d1e1d4682c00798bc67872205b89a2f8ba52cb95 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 6 Jul 2022 16:39:15 -0700 Subject: [PATCH 01/29] Formatting cleanup --- skaff/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skaff/README.md b/skaff/README.md index aed34190f64..5d7c1d02f35 100644 --- a/skaff/README.md +++ b/skaff/README.md @@ -2,4 +2,4 @@ `skaff` is a Terraform AWS Provider scaffolding command line tool. It generates resource/data source files and accompanying test files which adhere to the latest best practice. These files are heavily commented with instructions so serve as the best way to get started with provider development. -See the [Provider Scaffolding Documentation](https://hashicorp.github.io/terraform-provider-aws/skaff/) for details on how to use `skaff`. \ No newline at end of file +See the [Provider Scaffolding Documentation](https://hashicorp.github.io/terraform-provider-aws/skaff/) for details on how to use `skaff`. From c297f391edfdb4f10d8423084317f0cbd8e978f2 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 6 Jul 2022 17:18:07 -0700 Subject: [PATCH 02/29] Uses AWS SDK v2 for Comprehend --- go.mod | 9 +++++---- go.sum | 14 ++++++++++---- internal/conns/awsclient_gen.go | 4 ++-- internal/conns/config.go | 7 +++++++ internal/conns/config_gen.go | 2 -- names/names_data.csv | 2 +- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 40813efd742..674ad31f010 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.18 require ( github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 github.com/aws/aws-sdk-go v1.44.71 - github.com/aws/aws-sdk-go-v2 v1.16.9 + github.com/aws/aws-sdk-go-v2 v1.16.10 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.10 + github.com/aws/aws-sdk-go-v2/service/comprehend v1.18.6 github.com/aws/aws-sdk-go-v2/service/fis v1.12.10 github.com/aws/aws-sdk-go-v2/service/kendra v1.31.2 github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.0.2 @@ -45,14 +46,14 @@ require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.15.4 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.11 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.18.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 // indirect - github.com/aws/smithy-go v1.12.0 // indirect + github.com/aws/smithy-go v1.12.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v0.5.2 // indirect diff --git a/go.sum b/go.sum index 966534ef968..a97a1ffb1ac 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,9 @@ github.com/aws/aws-sdk-go v1.44.71 h1:e5ZbeFAdDB9i7NcQWdmIiA/NOC4aWec3syOUtUE0dB github.com/aws/aws-sdk-go v1.44.71/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= github.com/aws/aws-sdk-go-v2 v1.16.8/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.16.9 h1:Ezgkr/ByXA+5SASNJYLzA3kjbqXoho8QVDnCSHdgMBE= github.com/aws/aws-sdk-go-v2 v1.16.9/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= +github.com/aws/aws-sdk-go-v2 v1.16.10 h1:+yDD0tcuHRQZgqONkpDwzepqmElQaSlFPymHRHR9mrc= +github.com/aws/aws-sdk-go-v2 v1.16.10/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo= github.com/aws/aws-sdk-go-v2/config v1.15.4 h1:P4mesY1hYUxru4f9SU0XxNKXmzfxsD0FtMIPRBjkH7Q= github.com/aws/aws-sdk-go-v2/config v1.15.4/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= github.com/aws/aws-sdk-go-v2/credentials v1.12.0 h1:4R/NqlcRFSkR0wxOhgHi+agGpbEr5qMCjn7VqUIJY+E= @@ -35,14 +36,18 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.10 h1:x6v/oELu+w/USy9D6dmoPE github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.10/go.mod h1:nWH+8PK9k4NLsOvOb3wtbFHiYeLXCxRokv4JnUN+wWs= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.15/go.mod h1:pWrr2OoHlT7M/Pd2y4HV3gJyPb3qj5qMmnPkKSNPYK4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.16 h1:YVn7PaQ9La7tuTvKZGlXmzXkEHdbzHlKHeBrWCTtwtA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.16/go.mod h1:GV1J/d4oB2fKCEoWRlYBOI6qzfpH8IXQN1d/caQGaMo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.17 h1:U8DZvyFFesBmK62dYC6BRXm4Cd/wPP3aPcecu3xv/F4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.17/go.mod h1:6qtGip7sJEyvgsLjphRZWF9qPe3xJf1mL/MM01E35Wc= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.9/go.mod h1:08tUpeSGN33QKSO7fwxXczNfiwCpbj+GxK6XKwqWVv0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.10 h1:m7pKEgyn/TI2pn2WehrmJO4CKBaKvpkSLgnDR4nckC0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.10/go.mod h1:pucnblrb8XuRc/ZEi2S+jdQa3JVAfnwhytGgawh5pR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.11 h1:GMp98usVW5tzQhxd26KWhoNQPlR2noIlfbzqjVGBhLU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.11/go.mod h1:cYAfnB+9ZkmZWpQWmPDsuIGm4EA+6k2ZVtxKjw/XJBY= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 h1:6cZRymlLEIlDTEB0+5+An6Zj1CKt6rSE69tOmFeu1nk= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= +github.com/aws/aws-sdk-go-v2/service/comprehend v1.18.6 h1:XbmxKUDU1GhcusWsvASyAomd0X2Qx0ROQjuZLEGKl/4= +github.com/aws/aws-sdk-go-v2/service/comprehend v1.18.6/go.mod h1:Netkj9MGMytRWgWfxPpjVCEvMTETtzYqr3AshAHOoOg= github.com/aws/aws-sdk-go-v2/service/fis v1.12.10 h1:JPBj3hpFRhV1AYDNIqnt8JD9eflH7gAWGNAtRETKWu8= github.com/aws/aws-sdk-go-v2/service/fis v1.12.10/go.mod h1:kkuTKync7QCaQnYM7KhlZvlEPQWldpAaxhzqVKUEaJI= github.com/aws/aws-sdk-go-v2/service/iam v1.18.4 h1:E41guA79mjEbwJdh0zXz1d8+Zt4zxRr+b1ipiVbKXzs= @@ -62,8 +67,9 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsVig github.com/aws/aws-sdk-go-v2/service/transcribe v1.21.2 h1:tpxlVw/8WhfqUgY9Ia1BcrkwXRtYAOrgMupg4XHJ1GE= github.com/aws/aws-sdk-go-v2/service/transcribe v1.21.2/go.mod h1:RG8mbFe5gh0/r8LXxFjYt+GM5R2APw0VT91pvvEJ878= github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= -github.com/aws/smithy-go v1.12.0 h1:gXpeZel/jPoWQ7OEmLIgCUnhkFftqNfwWUwAHSlp1v0= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag= +github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index 4ea49b0b275..72e1947c442 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -4,6 +4,7 @@ package conns import ( "fmt" + "github.com/aws/aws-sdk-go-v2/service/comprehend" "github.com/aws/aws-sdk-go-v2/service/fis" "github.com/aws/aws-sdk-go-v2/service/kendra" "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" @@ -75,7 +76,6 @@ import ( "github.com/aws/aws-sdk-go/service/cognitoidentity" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" "github.com/aws/aws-sdk-go/service/cognitosync" - "github.com/aws/aws-sdk-go/service/comprehend" "github.com/aws/aws-sdk-go/service/comprehendmedical" "github.com/aws/aws-sdk-go/service/computeoptimizer" "github.com/aws/aws-sdk-go/service/configservice" @@ -385,7 +385,7 @@ type AWSClient struct { CognitoIDPConn *cognitoidentityprovider.CognitoIdentityProvider CognitoIdentityConn *cognitoidentity.CognitoIdentity CognitoSyncConn *cognitosync.CognitoSync - ComprehendConn *comprehend.Comprehend + ComprehendConn *comprehend.Client ComprehendMedicalConn *comprehendmedical.ComprehendMedical ComputeOptimizerConn *computeoptimizer.ComputeOptimizer ConfigServiceConn *configservice.ConfigService diff --git a/internal/conns/config.go b/internal/conns/config.go index 2f05f3a3bf3..14dc26dd8ce 100644 --- a/internal/conns/config.go +++ b/internal/conns/config.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/aws-sdk-go-v2/service/comprehend" "github.com/aws/aws-sdk-go-v2/service/fis" "github.com/aws/aws-sdk-go-v2/service/kendra" "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" @@ -194,6 +195,12 @@ func (c *Config) Client(ctx context.Context) (interface{}, diag.Diagnostics) { client.Session = sess client.TerraformVersion = c.TerraformVersion + client.ComprehendConn = comprehend.NewFromConfig(cfg, func(o *comprehend.Options) { + if endpoint := c.Endpoints[names.Comprehend]; endpoint != "" { + o.EndpointResolver = comprehend.EndpointResolverFromURL(endpoint) + } + }) + client.FISConn = fis.NewFromConfig(cfg, func(o *fis.Options) { if endpoint := c.Endpoints[names.FIS]; endpoint != "" { o.EndpointResolver = fis.EndpointResolverFromURL(endpoint) diff --git a/internal/conns/config_gen.go b/internal/conns/config_gen.go index 45a12941182..7e078fa6ff2 100644 --- a/internal/conns/config_gen.go +++ b/internal/conns/config_gen.go @@ -69,7 +69,6 @@ import ( "github.com/aws/aws-sdk-go/service/cognitoidentity" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" "github.com/aws/aws-sdk-go/service/cognitosync" - "github.com/aws/aws-sdk-go/service/comprehend" "github.com/aws/aws-sdk-go/service/comprehendmedical" "github.com/aws/aws-sdk-go/service/computeoptimizer" "github.com/aws/aws-sdk-go/service/configservice" @@ -360,7 +359,6 @@ func (c *Config) clientConns(sess *session.Session) *AWSClient { CognitoIDPConn: cognitoidentityprovider.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CognitoIDP])})), CognitoIdentityConn: cognitoidentity.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CognitoIdentity])})), CognitoSyncConn: cognitosync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CognitoSync])})), - ComprehendConn: comprehend.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Comprehend])})), ComprehendMedicalConn: comprehendmedical.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ComprehendMedical])})), ComputeOptimizerConn: computeoptimizer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ComputeOptimizer])})), ConfigServiceConn: configservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ConfigService])})), diff --git a/names/names_data.csv b/names/names_data.csv index 8e1b98c378d..184e107caba 100644 --- a/names/names_data.csv +++ b/names/names_data.csv @@ -80,7 +80,7 @@ codestar-notifications,codestarnotifications,codestarnotifications,codestarnotif cognito-identity,cognitoidentity,cognitoidentity,cognitoidentity,,cognitoidentity,,,CognitoIdentity,CognitoIdentity,,1,aws_cognito_identity_(?!provider),aws_cognitoidentity_,,cognito_identity_pool,Cognito Identity,Amazon,,,,, cognito-idp,cognitoidp,cognitoidentityprovider,cognitoidentityprovider,,cognitoidp,,cognitoidentityprovider,CognitoIDP,CognitoIdentityProvider,,1,aws_cognito_(identity_provider|resource|user|risk),aws_cognitoidp_,,cognito_identity_provider;cognito_resource_;cognito_user;cognito_risk,Cognito IDP (Identity Provider),Amazon,,,,, cognito-sync,cognitosync,cognitosync,cognitosync,,cognitosync,,,CognitoSync,CognitoSync,,1,,aws_cognitosync_,,cognitosync_,Cognito Sync,Amazon,,,,, -comprehend,comprehend,comprehend,comprehend,,comprehend,,,Comprehend,Comprehend,,1,,aws_comprehend_,,comprehend_,Comprehend,Amazon,,,,, +comprehend,comprehend,comprehend,comprehend,,comprehend,,,Comprehend,Comprehend,x,2,,aws_comprehend_,,comprehend_,Comprehend,Amazon,,,,, comprehendmedical,comprehendmedical,comprehendmedical,comprehendmedical,,comprehendmedical,,,ComprehendMedical,ComprehendMedical,,1,,aws_comprehendmedical_,,comprehendmedical_,Comprehend Medical,Amazon,,,,, compute-optimizer,computeoptimizer,computeoptimizer,computeoptimizer,,computeoptimizer,,,ComputeOptimizer,ComputeOptimizer,,1,,aws_computeoptimizer_,,computeoptimizer_,Compute Optimizer,AWS,,,,, configservice,configservice,configservice,configservice,,configservice,,config,ConfigService,ConfigService,,1,aws_config_,aws_configservice_,,config_,Config,AWS,,,,, From 8af35054b5f3afcc75254751007c1b5ba21d1b15 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 6 Jul 2022 17:37:31 -0700 Subject: [PATCH 03/29] Adds tag code generation --- .ci/.semgrep-service-name0.yml | 63 ++++++++----- .ci/.semgrep-service-name1.yml | 75 +++++++++------ .ci/.semgrep-service-name2.yml | 44 ++++++--- .ci/.semgrep-service-name3.yml | 14 +++ .../components/generated/services_all.kt | 1 + internal/service/comprehend/generate.go | 4 + internal/service/comprehend/tags_gen.go | 94 +++++++++++++++++++ 7 files changed, 226 insertions(+), 69 deletions(-) create mode 100644 internal/service/comprehend/generate.go create mode 100644 internal/service/comprehend/tags_gen.go diff --git a/.ci/.semgrep-service-name0.yml b/.ci/.semgrep-service-name0.yml index 5c859afa1c9..350dcaee5b2 100644 --- a/.ci/.semgrep-service-name0.yml +++ b/.ci/.semgrep-service-name0.yml @@ -2896,92 +2896,105 @@ rules: patterns: - pattern-regex: "(?i)CognitoIDP" severity: WARNING - - id: configservice-in-func-name + - id: comprehend-in-func-name languages: - go - message: Do not use "ConfigService" in func name inside configservice package + message: Do not use "Comprehend" in func name inside comprehend package paths: include: - - internal/service/configservice + - internal/service/comprehend patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: metavariable: $NAME patterns: - - pattern-regex: "(?i)ConfigService" + - pattern-regex: "(?i)Comprehend" - pattern-not-regex: ^TestAcc.* severity: WARNING - - id: configservice-in-test-name + - id: comprehend-in-test-name languages: - go - message: Include "ConfigService" in test name + message: Include "Comprehend" in test name paths: include: - - internal/service/configservice/*_test.go + - internal/service/comprehend/*_test.go patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: metavariable: $NAME patterns: - - pattern-not-regex: "^TestAccConfigService" + - pattern-not-regex: "^TestAccComprehend" - pattern-regex: ^TestAcc.* severity: WARNING - - id: configservice-in-const-name + - id: comprehend-in-const-name languages: - go - message: Do not use "ConfigService" in const name inside configservice package + message: Do not use "Comprehend" in const name inside comprehend package paths: include: - - internal/service/configservice + - internal/service/comprehend patterns: - pattern: const $NAME = ... - metavariable-pattern: metavariable: $NAME patterns: - - pattern-regex: "(?i)ConfigService" + - pattern-regex: "(?i)Comprehend" severity: WARNING - - id: configservice-in-var-name + - id: comprehend-in-var-name languages: - go - message: Do not use "ConfigService" in var name inside configservice package + message: Do not use "Comprehend" in var name inside comprehend package paths: include: - - internal/service/configservice + - internal/service/comprehend patterns: - pattern: var $NAME = ... - metavariable-pattern: metavariable: $NAME patterns: - - pattern-regex: "(?i)ConfigService" + - pattern-regex: "(?i)Comprehend" severity: WARNING - - id: connect-in-func-name + - id: configservice-in-func-name languages: - go - message: Do not use "Connect" in func name inside connect package + message: Do not use "ConfigService" in func name inside configservice package paths: include: - - internal/service/connect + - internal/service/configservice patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: metavariable: $NAME patterns: - - pattern-regex: "(?i)Connect" - - pattern-not-regex: .*uickConnect.* + - pattern-regex: "(?i)ConfigService" - pattern-not-regex: ^TestAcc.* severity: WARNING - - id: connect-in-test-name + - id: configservice-in-test-name languages: - go - message: Include "Connect" in test name + message: Include "ConfigService" in test name paths: include: - - internal/service/connect/*_test.go + - internal/service/configservice/*_test.go patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: metavariable: $NAME patterns: - - pattern-not-regex: "^TestAccConnect" + - pattern-not-regex: "^TestAccConfigService" - pattern-regex: ^TestAcc.* severity: WARNING + - id: configservice-in-const-name + languages: + - go + message: Do not use "ConfigService" in const name inside configservice package + paths: + include: + - internal/service/configservice + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)ConfigService" + severity: WARNING diff --git a/.ci/.semgrep-service-name1.yml b/.ci/.semgrep-service-name1.yml index a1df360e29d..b5895bfc98b 100644 --- a/.ci/.semgrep-service-name1.yml +++ b/.ci/.semgrep-service-name1.yml @@ -1,5 +1,50 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: + - id: configservice-in-var-name + languages: + - go + message: Do not use "ConfigService" in var name inside configservice package + paths: + include: + - internal/service/configservice + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)ConfigService" + severity: WARNING + - id: connect-in-func-name + languages: + - go + message: Do not use "Connect" in func name inside connect package + paths: + include: + - internal/service/connect + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Connect" + - pattern-not-regex: .*uickConnect.* + - pattern-not-regex: ^TestAcc.* + severity: WARNING + - id: connect-in-test-name + languages: + - go + message: Include "Connect" in test name + paths: + include: + - internal/service/connect/*_test.go + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccConnect" + - pattern-regex: ^TestAcc.* + severity: WARNING - id: connect-in-const-name languages: - go @@ -2957,33 +3002,3 @@ rules: patterns: - pattern-regex: "(?i)ImageBuilder" severity: WARNING - - id: inspector-in-func-name - languages: - - go - message: Do not use "Inspector" in func name inside inspector package - paths: - include: - - internal/service/inspector - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Inspector" - - pattern-not-regex: ^TestAcc.* - severity: WARNING - - id: inspector-in-test-name - languages: - - go - message: Include "Inspector" in test name - paths: - include: - - internal/service/inspector/*_test.go - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-not-regex: "^TestAccInspector" - - pattern-regex: ^TestAcc.* - severity: WARNING diff --git a/.ci/.semgrep-service-name2.yml b/.ci/.semgrep-service-name2.yml index fea9f26de7d..375790c0ce9 100644 --- a/.ci/.semgrep-service-name2.yml +++ b/.ci/.semgrep-service-name2.yml @@ -1,5 +1,35 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: + - id: inspector-in-func-name + languages: + - go + message: Do not use "Inspector" in func name inside inspector package + paths: + include: + - internal/service/inspector + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Inspector" + - pattern-not-regex: ^TestAcc.* + severity: WARNING + - id: inspector-in-test-name + languages: + - go + message: Include "Inspector" in test name + paths: + include: + - internal/service/inspector/*_test.go + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccInspector" + - pattern-regex: ^TestAcc.* + severity: WARNING - id: inspector-in-const-name languages: - go @@ -2968,17 +2998,3 @@ rules: patterns: - pattern-regex: "(?i)Redshift" severity: WARNING - - id: redshift-in-var-name - languages: - - go - message: Do not use "Redshift" in var name inside redshift package - paths: - include: - - internal/service/redshift - patterns: - - pattern: var $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - severity: WARNING diff --git a/.ci/.semgrep-service-name3.yml b/.ci/.semgrep-service-name3.yml index f953bae758f..25efae7d9b4 100644 --- a/.ci/.semgrep-service-name3.yml +++ b/.ci/.semgrep-service-name3.yml @@ -1,5 +1,19 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: + - id: redshift-in-var-name + languages: + - go + message: Do not use "Redshift" in var name inside redshift package + paths: + include: + - internal/service/redshift + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + severity: WARNING - id: redshiftdata-in-func-name languages: - go diff --git a/.teamcity/components/generated/services_all.kt b/.teamcity/components/generated/services_all.kt index e60565fe69f..51bf5a47db0 100644 --- a/.teamcity/components/generated/services_all.kt +++ b/.teamcity/components/generated/services_all.kt @@ -42,6 +42,7 @@ val services = mapOf( "codestarnotifications" to ServiceSpec("CodeStar Notifications"), "cognitoidentity" to ServiceSpec("Cognito Identity"), "cognitoidp" to ServiceSpec("Cognito IDP (Identity Provider)"), + "comprehend" to ServiceSpec("Comprehend"), "configservice" to ServiceSpec("Config"), "connect" to ServiceSpec("Connect"), "cur" to ServiceSpec("Cost and Usage Report"), diff --git a/internal/service/comprehend/generate.go b/internal/service/comprehend/generate.go new file mode 100644 index 00000000000..3b46c086b6f --- /dev/null +++ b/internal/service/comprehend/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../../generate/tags/main.go -ServiceTagsSlice -ListTags -UpdateTags -AWSSDKVersion=2 +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package comprehend diff --git a/internal/service/comprehend/tags_gen.go b/internal/service/comprehend/tags_gen.go new file mode 100644 index 00000000000..eef45629b09 --- /dev/null +++ b/internal/service/comprehend/tags_gen.go @@ -0,0 +1,94 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package comprehend + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/comprehend" + "github.com/aws/aws-sdk-go-v2/service/comprehend/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +// ListTags lists comprehend service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func ListTags(ctx context.Context, conn *comprehend.Client, identifier string) (tftags.KeyValueTags, error) { + input := &comprehend.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(ctx, input) + + if err != nil { + return tftags.New(nil), err + } + + return KeyValueTags(output.Tags), nil +} + +// []*SERVICE.Tag handling + +// Tags returns comprehend service tags. +func Tags(tags tftags.KeyValueTags) []types.Tag { + result := make([]types.Tag, 0, len(tags)) + + for k, v := range tags.Map() { + tag := types.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + + result = append(result, tag) + } + + return result +} + +// KeyValueTags creates tftags.KeyValueTags from comprehend service tags. +func KeyValueTags(tags []types.Tag) tftags.KeyValueTags { + m := make(map[string]*string, len(tags)) + + for _, tag := range tags { + m[aws.ToString(tag.Key)] = tag.Value + } + + return tftags.New(m) +} + +// UpdateTags updates comprehend service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func UpdateTags(ctx context.Context, conn *comprehend.Client, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := tftags.New(oldTagsMap) + newTags := tftags.New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &comprehend.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: removedTags.IgnoreAWS().Keys(), + } + + _, err := conn.UntagResource(ctx, input) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &comprehend.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: Tags(updatedTags.IgnoreAWS()), + } + + _, err := conn.TagResource(ctx, input) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} From c85a44abaad411d2d62d9e36ba2abd0bcb67276e Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 18 Jul 2022 16:14:39 -0700 Subject: [PATCH 04/29] Adds `aws_comprehend_entity_recognizer` with basic CRD and Import --- internal/provider/provider.go | 3 + internal/service/comprehend/consts.go | 7 + .../service/comprehend/entity_recognizer.go | 805 +++++++++++++ .../comprehend/entity_recognizer_test.go | 298 +++++ internal/service/comprehend/flex.go | 25 + .../entity_recognizer/documents.txt | 1000 ++++++++++++++++ .../entity_recognizer/entitylist.csv | 1001 +++++++++++++++++ internal/service/comprehend/validate.go | 19 + ...comprehend_entity_recognizer.html.markdown | 151 +++ 9 files changed, 3309 insertions(+) create mode 100644 internal/service/comprehend/consts.go create mode 100644 internal/service/comprehend/entity_recognizer.go create mode 100644 internal/service/comprehend/entity_recognizer_test.go create mode 100644 internal/service/comprehend/flex.go create mode 100644 internal/service/comprehend/test-fixtures/entity_recognizer/documents.txt create mode 100644 internal/service/comprehend/test-fixtures/entity_recognizer/entitylist.csv create mode 100644 internal/service/comprehend/validate.go create mode 100644 website/docs/r/comprehend_entity_recognizer.html.markdown diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d47b93e53e7..f7f1c1065e7 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -56,6 +56,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/codestarnotifications" "github.com/hashicorp/terraform-provider-aws/internal/service/cognitoidentity" "github.com/hashicorp/terraform-provider-aws/internal/service/cognitoidp" + "github.com/hashicorp/terraform-provider-aws/internal/service/comprehend" "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" "github.com/hashicorp/terraform-provider-aws/internal/service/connect" "github.com/hashicorp/terraform-provider-aws/internal/service/cur" @@ -1174,6 +1175,8 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_cognito_user_pool_domain": cognitoidp.ResourceUserPoolDomain(), "aws_cognito_user_pool_ui_customization": cognitoidp.ResourceUserPoolUICustomization(), + "aws_comprehend_entity_recognizer": comprehend.ResourceEntityRecognizer(), + "aws_config_aggregate_authorization": configservice.ResourceAggregateAuthorization(), "aws_config_config_rule": configservice.ResourceConfigRule(), "aws_config_configuration_aggregator": configservice.ResourceConfigurationAggregator(), diff --git a/internal/service/comprehend/consts.go b/internal/service/comprehend/consts.go new file mode 100644 index 00000000000..d2bf6629ad7 --- /dev/null +++ b/internal/service/comprehend/consts.go @@ -0,0 +1,7 @@ +package comprehend + +import ( + "time" +) + +const iamPropagationTimeout = 2 * time.Minute diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go new file mode 100644 index 00000000000..0423f7801de --- /dev/null +++ b/internal/service/comprehend/entity_recognizer.go @@ -0,0 +1,805 @@ +package comprehend + +import ( + "context" + "errors" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/comprehend" + "github.com/aws/aws-sdk-go-v2/service/comprehend/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceEntityRecognizer() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceEntityRecognizerCreate, + ReadWithoutTimeout: resourceEntityRecognizerRead, + UpdateWithoutTimeout: resourceEntityRecognizerUpdate, + DeleteWithoutTimeout: resourceEntityRecognizerDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "data_access_role_arn": { + Type: schema.TypeString, + Required: true, + }, + "input_data_config": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "annotations": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "s3_uri": { + Type: schema.TypeString, + Required: true, + }, + "test_s3_uri": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "augmented_manifests": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "annotation_data_s3_uri": { + Type: schema.TypeString, + Optional: true, + }, + "attribute_names": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "document_type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.AugmentedManifestsDocumentTypeFormat](), + Default: types.AugmentedManifestsDocumentTypeFormatPlainTextDocument, + }, + "s3_uri": { + Type: schema.TypeString, + Required: true, + }, + "source_documents_s3_uri": { + Type: schema.TypeString, + Optional: true, + }, + "split": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.Split](), + Default: types.SplitTrain, + }, + }, + }, + }, + "data_format": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.EntityRecognizerDataFormat](), + Default: types.EntityRecognizerDataFormatComprehendCsv, + }, + "documents": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "input_format": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.InputFormat](), + Default: types.InputFormatOneDocPerLine, + }, + "s3_uri": { + Type: schema.TypeString, + Required: true, + }, + "test_s3_uri": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "entity_list": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "s3_uri": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "entity_types": { + Type: schema.TypeSet, + Required: true, + MaxItems: 25, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringIsNotEmpty, + validation.StringDoesNotContainAny("\n\r\t,"), + ), + }, + }, + }, + }, + }, + }, + }, + "language_code": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.SyntaxLanguageCode](), + }, + "model_kms_key_id": { + Type: schema.TypeString, + Optional: true, + }, + "model_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validModelName, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "version_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validModelVersionName, + }, + "volume_kms_key_id": { + Type: schema.TypeString, + Optional: true, + }, + "vpc_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "security_group_ids": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "subnets": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ComprehendConn + + in := &comprehend.CreateEntityRecognizerInput{ + DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), + InputDataConfig: expandInputDataConfig(d.Get("input_data_config").([]interface{})), + LanguageCode: types.LanguageCode(d.Get("language_code").(string)), + RecognizerName: aws.String(d.Get("name").(string)), + VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), + ClientRequestToken: aws.String(resource.UniqueId()), + } + + if v, ok := d.Get("model_kms_key_id").(string); ok && v != "" { + in.ModelKmsKeyId = aws.String(v) + } + + if v, ok := d.Get("model_policy").(string); ok && v != "" { + in.ModelPolicy = aws.String(v) + } + + if v, ok := d.Get("version_name").(string); ok && v != "" { + in.VersionName = aws.String(v) + } + + if v, ok := d.Get("volume_kms_key_id").(string); ok && v != "" { + in.VolumeKmsKeyId = aws.String(v) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } + + // Because the IAM credentials aren't evaluated until later, we need to ensure we wait for the IAM propagation delay + time.Sleep(iamPropagationTimeout) + + out, err := conn.CreateEntityRecognizer(ctx, in) + if err != nil { + return diag.Errorf("creating Amazon Comprehend Entity Recognizer (%s): %s", d.Get("name").(string), err) + } + + if out == nil || out.EntityRecognizerArn == nil { + return diag.Errorf("creating Amazon Comprehend Entity Recognizer (%s): empty output", d.Get("name").(string)) + } + + d.SetId(aws.ToString(out.EntityRecognizerArn)) + + if _, err := waitEntityRecognizerCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + } + + return resourceEntityRecognizerRead(ctx, d, meta) +} + +func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ComprehendConn + + out, err := FindEntityRecognizerByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Comprehend Entity Recognizer (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + + d.Set("arn", out.EntityRecognizerArn) + d.Set("data_access_role_arn", out.DataAccessRoleArn) + d.Set("language_code", out.LanguageCode) + d.Set("model_kms_key_id", out.ModelKmsKeyId) + d.Set("version_name", out.VersionName) + d.Set("volume_kms_key_id", out.VolumeKmsKeyId) + + // DescribeEntityRecognizer() doesn't return the model name + arn, err := arn.Parse(aws.ToString(out.EntityRecognizerArn)) + if err != nil { + return diag.Errorf("reading Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + re := regexp.MustCompile(`entity-recognizer/(.*)`) + name := re.FindStringSubmatch(arn.Resource)[1] + d.Set("name", name) + + if err := d.Set("input_data_config", flattenInputDataConfig(out.InputDataConfig)); err != nil { + return diag.Errorf("setting input_data_config: %s", err) + } + + if err := d.Set("vpc_config", flattenVPCConfig(out.VpcConfig)); err != nil { + return diag.Errorf("setting vpc_config: %s", err) + } + + // TODO + // p, err := verify.SecondJSONUnlessEquivalent(d.Get("model_policy").(string), aws.ToString(out.ModelPolicy)) + // if err != nil { + // return diag.Errorf("while setting model_policy (%s), encountered: %s", p, err) + // } + + // p, err = structure.NormalizeJsonString(p) + // if err != nil { + // return diag.Errorf("policy (%s) is invalid JSON: %s", p, err) + // } + + // d.Set("model_policy", p) + + tags, err := ListTags(ctx, conn, d.Id()) + if err != nil { + return diag.Errorf("listing tags for Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("setting tags_all: %s", err) + } + + return nil +} + +func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ComprehendConn + + log.Printf("[INFO] Stopping Comprehend Entity Recognizer (%s)", d.Id()) + + _, err := conn.StopTrainingEntityRecognizer(ctx, &comprehend.StopTrainingEntityRecognizerInput{ + EntityRecognizerArn: aws.String(d.Id()), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return diag.Errorf("stopping Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + + if _, err := waitEntityRecognizerStopped(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return diag.Errorf("waiting for Comprehend Entity Recognizer (%s) to be deleted: %s", d.Id(), err) + } + + log.Printf("[INFO] Deleting Comprehend Entity Recognizer (%s)", d.Id()) + + _, err = conn.DeleteEntityRecognizer(ctx, &comprehend.DeleteEntityRecognizerInput{ + EntityRecognizerArn: aws.String(d.Id()), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return diag.Errorf("deleting Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + + if _, err := waitEntityRecognizerDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for Comprehend Entity Recognizer (%s) to be deleted: %s", d.Id(), err) + } + + return nil +} + +func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{}, + Target: enum.Slice(types.ModelStatusSubmitted), + Refresh: statusEntityRecognizer(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*types.EntityRecognizerProperties); ok { + return out, err + } + + return nil, err +} + +func waitEntityRecognizerStopped(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { + stateConf := &resource.StateChangeConf{ + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusStopRequested), + Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError), + Refresh: statusEntityRecognizer(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*types.EntityRecognizerProperties); ok { + return out, err + } + + return nil, err +} + +func waitEntityRecognizerDeleted(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { + stateConf := &resource.StateChangeConf{ + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusInError, types.ModelStatusStopRequested), + Target: []string{}, + Refresh: statusEntityRecognizer(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*types.EntityRecognizerProperties); ok { + return out, err + } + + return nil, err +} + +func statusEntityRecognizer(ctx context.Context, conn *comprehend.Client, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := FindEntityRecognizerByID(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Status), nil + } +} + +func FindEntityRecognizerByID(ctx context.Context, conn *comprehend.Client, id string) (*types.EntityRecognizerProperties, error) { + in := &comprehend.DescribeEntityRecognizerInput{ + EntityRecognizerArn: aws.String(id), + } + + out, err := conn.DescribeEntityRecognizer(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.EntityRecognizerProperties == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out.EntityRecognizerProperties, nil +} + +func flattenInputDataConfig(apiObject *types.EntityRecognizerInputDataConfig) []interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "entity_types": flattenEntityTypes(apiObject.EntityTypes), + "annotations": flattenAnnotations(apiObject.Annotations), + "augmented_manifests": flattenAugmentedManifests(apiObject.AugmentedManifests), + "data_format": apiObject.DataFormat, + "documents": flattenDocuments(apiObject.Documents), + "entity_list": flattenEntityList(apiObject.EntityList), + } + + return []interface{}{m} +} + +func flattenEntityTypes(apiObjects []types.EntityTypesListItem) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + l = append(l, flattenEntityTypesListItem(&apiObject)) + } + + return l +} + +func flattenEntityTypesListItem(apiObject *types.EntityTypesListItem) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "type": aws.ToString(apiObject.Type), + } + + return m +} + +func flattenAnnotations(apiObject *types.EntityRecognizerAnnotations) []interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "s3_uri": aws.ToString(apiObject.S3Uri), + } + + if v := apiObject.TestS3Uri; v != nil { + m["test_s3_uri"] = aws.ToString(v) + } + + return []interface{}{m} +} + +func flattenAugmentedManifests(apiObjects []types.AugmentedManifestsListItem) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + l = append(l, flattenAugmentedManifestsListItem(&apiObject)) + } + + return l +} + +func flattenAugmentedManifestsListItem(apiObject *types.AugmentedManifestsListItem) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "attribute_names": FlattenStringList(apiObject.AttributeNames), + "s3_uri": aws.ToString(apiObject.S3Uri), + "document_type": apiObject.DocumentType, + "split": apiObject.Split, + } + + if v := apiObject.AnnotationDataS3Uri; v != nil { + m["annotation_data_s3_uri"] = aws.ToString(v) + } + + if v := apiObject.SourceDocumentsS3Uri; v != nil { + m["source_documents_s3_uri"] = aws.ToString(v) + } + + return m +} + +func flattenDocuments(apiObject *types.EntityRecognizerDocuments) []interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "s3_uri": aws.ToString(apiObject.S3Uri), + "input_format": apiObject.InputFormat, + } + + if v := apiObject.TestS3Uri; v != nil { + m["test_s3_uri"] = aws.ToString(v) + } + + return []interface{}{m} +} + +func flattenEntityList(apiObject *types.EntityRecognizerEntityList) []interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "s3_uri": aws.ToString(apiObject.S3Uri), + } + + return []interface{}{m} +} + +func flattenVPCConfig(apiObject *types.VpcConfig) []interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "security_group_ids": apiObject.SecurityGroupIds, + "subnets": apiObject.Subnets, + } + + return []interface{}{m} +} + +func expandInputDataConfig(tfList []interface{}) *types.EntityRecognizerInputDataConfig { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + a := &types.EntityRecognizerInputDataConfig{ + EntityTypes: expandEntityTypes(tfMap["entity_types"].(*schema.Set)), + Annotations: expandAnnotations(tfMap["annotations"].([]interface{})), + AugmentedManifests: expandAugmentedManifests(tfMap["augmented_manifests"].([]interface{})), + DataFormat: types.EntityRecognizerDataFormat(tfMap["data_format"].(string)), + Documents: expandDocuments(tfMap["documents"].([]interface{})), + EntityList: expandEntityList(tfMap["entity_list"].([]interface{})), + } + + return a +} + +func expandEntityTypes(tfSet *schema.Set) []types.EntityTypesListItem { + if tfSet.Len() == 0 { + return nil + } + + var s []types.EntityTypesListItem + + for _, r := range tfSet.List() { + m, ok := r.(map[string]interface{}) + if !ok { + continue + } + + a := expandEntityTypesListItem(m) + if a == nil { + continue + } + + s = append(s, *a) + } + + return s +} + +func expandEntityTypesListItem(tfMap map[string]interface{}) *types.EntityTypesListItem { + if tfMap == nil { + return nil + } + + a := &types.EntityTypesListItem{} + + if v, ok := tfMap["type"].(string); ok && v != "" { + a.Type = aws.String(v) + } + + return a +} + +func expandAnnotations(tfList []interface{}) *types.EntityRecognizerAnnotations { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + a := &types.EntityRecognizerAnnotations{ + S3Uri: aws.String(tfMap["s3_uri"].(string)), + } + + if v, ok := tfMap["test_s3_uri"].(string); ok && v != "" { + a.TestS3Uri = aws.String(v) + } + + return a +} + +func expandAugmentedManifests(tfList []interface{}) []types.AugmentedManifestsListItem { + if len(tfList) == 0 { + return nil + } + + var s []types.AugmentedManifestsListItem + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + a := expandAugmentedManifestsListItem(m) + + if a == nil { + continue + } + + s = append(s, *a) + } + + return s +} + +func expandAugmentedManifestsListItem(tfMap map[string]interface{}) *types.AugmentedManifestsListItem { + if tfMap == nil { + return nil + } + + a := &types.AugmentedManifestsListItem{ + AttributeNames: ExpandStringList(tfMap["attribute_names"].([]interface{})), + S3Uri: aws.String(tfMap["s3_uri"].(string)), + DocumentType: types.AugmentedManifestsDocumentTypeFormat(tfMap["document_type"].(string)), + Split: types.Split(tfMap["split"].(string)), + } + + if v, ok := tfMap["annotation_data_s3_uri"].(string); ok && v != "" { + a.AnnotationDataS3Uri = aws.String(v) + } + + if v, ok := tfMap["source_documents_s3_uri"].(string); ok && v != "" { + a.SourceDocumentsS3Uri = aws.String(v) + } + + return a +} + +func expandDocuments(tfList []interface{}) *types.EntityRecognizerDocuments { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + a := &types.EntityRecognizerDocuments{ + S3Uri: aws.String(tfMap["s3_uri"].(string)), + InputFormat: types.InputFormat(tfMap["input_format"].(string)), + } + + if v, ok := tfMap["test_s3_uri"].(string); ok && v != "" { + a.TestS3Uri = aws.String(v) + } + + return a +} + +func expandEntityList(tfList []interface{}) *types.EntityRecognizerEntityList { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + a := &types.EntityRecognizerEntityList{ + S3Uri: aws.String(tfMap["s3_uri"].(string)), + } + + return a +} + +func expandVPCConfig(tfList []interface{}) *types.VpcConfig { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + a := &types.VpcConfig{ + SecurityGroupIds: ExpandStringList(tfMap["security_group_ids"].([]interface{})), + Subnets: ExpandStringList(tfMap["subnets"].([]interface{})), + } + + return a +} diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go new file mode 100644 index 00000000000..5fd3e5ddc07 --- /dev/null +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -0,0 +1,298 @@ +package comprehend_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/comprehend" + "github.com/aws/aws-sdk-go-v2/service/comprehend/types" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfcomprehend "github.com/hashicorp/terraform-provider-aws/internal/service/comprehend" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccComprehendEntityRecognizer_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, comprehend.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s`, rName))), + resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.annotations.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.augmented_manifests.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.data_format", string(types.EntityRecognizerDataFormatComprehendCsv)), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "language_code", "en"), + resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), + resource.TestCheckNoResourceAttr(resourceName, "model_policy"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), + resource.TestCheckResourceAttr(resourceName, "version_name", ""), + resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, comprehend.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + acctest.CheckResourceDisappears(acctest.Provider, tfcomprehend.ResourceEntityRecognizer(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +// TODO: test deletion from in-error state. Try insufficient permissions to force error + +// TODO: add test for catching, e.g. permission errors in training + +func testAccCheckEntityRecognizerDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_comprehend_entity_recognizer" { + continue + } + + _, err := tfcomprehend.FindEntityRecognizerByID(ctx, conn, rs.Primary.ID) + if err != nil { + if tfresource.NotFound(err) { + return nil + } + return err + } + + return fmt.Errorf("Expected Comprehend Entity Recognizer to be destroyed, %s found", rs.Primary.ID) + } + + return nil +} + +func testAccCheckEntityRecognizerExists(name string, entityrecognizer *types.EntityRecognizerProperties) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Comprehend Entity Recognizer is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn + ctx := context.Background() + + resp, err := tfcomprehend.FindEntityRecognizerByID(ctx, conn, rs.Primary.ID) + if err != nil { + return fmt.Errorf("Error describing Comprehend Entity Recognizer: %w", err) + } + + *entityrecognizer = *resp + + return nil + } +} + +func testAccPreCheck(t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn + ctx := context.Background() + + input := &comprehend.ListEntityRecognizersInput{} + + _, err := conn.ListEntityRecognizers(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +// func testAccCheckEntityRecognizerNotRecreated(before, after *types.EntityRecognizerProperties) resource.TestCheckFunc { +// return func(s *terraform.State) error { +// if before, after := aws.StringValue(before.EntityRecognizerId), aws.StringValue(after.EntityRecognizerId); before != after { +// return fmt.Errorf("Comprehend Entity Recognizer (%s/%s) recreated", before, after) +// } + +// return nil +// } +// } + +func testAccEntityRecognizerConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_bucket" "test" { + bucket = %[1]q +} + +resource "aws_s3_bucket_public_access_block" "test" { + bucket = aws_s3_bucket.test.bucket + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_ownership_controls" "test" { + bucket = aws_s3_bucket.test.bucket + + rule { + object_ownership = "BucketOwnerEnforced" + } +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerBasicRoleConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < Date: Tue, 19 Jul 2022 14:50:38 -0700 Subject: [PATCH 05/29] Moves `testAccPreCheck` to separate file --- internal/service/comprehend/acc_test.go | 27 +++++++++++++++++++ .../comprehend/entity_recognizer_test.go | 17 ------------ 2 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 internal/service/comprehend/acc_test.go diff --git a/internal/service/comprehend/acc_test.go b/internal/service/comprehend/acc_test.go new file mode 100644 index 00000000000..10cd09a6652 --- /dev/null +++ b/internal/service/comprehend/acc_test.go @@ -0,0 +1,27 @@ +package comprehend_test + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/comprehend" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +func testAccPreCheck(t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn + ctx := context.Background() + + input := &comprehend.ListEntityRecognizersInput{} + + _, err := conn.ListEntityRecognizers(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 5fd3e5ddc07..bed6bd16a08 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -150,23 +150,6 @@ func testAccCheckEntityRecognizerExists(name string, entityrecognizer *types.Ent } } -func testAccPreCheck(t *testing.T) { - conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn - ctx := context.Background() - - input := &comprehend.ListEntityRecognizersInput{} - - _, err := conn.ListEntityRecognizers(ctx, input) - - if acctest.PreCheckSkipError(err) { - t.Skipf("skipping acceptance testing: %s", err) - } - - if err != nil { - t.Fatalf("unexpected PreCheck error: %s", err) - } -} - // func testAccCheckEntityRecognizerNotRecreated(before, after *types.EntityRecognizerProperties) resource.TestCheckFunc { // return func(s *terraform.State) error { // if before, after := aws.StringValue(before.EntityRecognizerId), aws.StringValue(after.EntityRecognizerId); before != after { From a335affbe87e26fe7d2f3f5bcb870cd5a9fa80f5 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 19 Jul 2022 14:51:40 -0700 Subject: [PATCH 06/29] Adds Comprehend endpoint ID --- internal/service/comprehend/entity_recognizer_test.go | 10 ++++++---- names/names.go | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index bed6bd16a08..f5b3e950e28 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -6,7 +6,6 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go-v2/service/comprehend" "github.com/aws/aws-sdk-go-v2/service/comprehend/types" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -15,6 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" tfcomprehend "github.com/hashicorp/terraform-provider-aws/internal/service/comprehend" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccComprehendEntityRecognizer_basic(t *testing.T) { @@ -29,9 +29,10 @@ func TestAccComprehendEntityRecognizer_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) testAccPreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, comprehend.ServiceID), + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckEntityRecognizerDestroy, Steps: []resource.TestStep{ @@ -80,15 +81,16 @@ func TestAccComprehendEntityRecognizer_disappears(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) testAccPreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, comprehend.ServiceID), + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckEntityRecognizerDestroy, Steps: []resource.TestStep{ { Config: testAccEntityRecognizerConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), acctest.CheckResourceDisappears(acctest.Provider, tfcomprehend.ResourceEntityRecognizer(), resourceName), ), diff --git a/names/names.go b/names/names.go index 061a5d9862c..da36d69acaf 100644 --- a/names/names.go +++ b/names/names.go @@ -23,6 +23,7 @@ import ( // This "should" be defined by the AWS Go SDK v2, but currently isn't. const ( + ComprehendEndpointID = "comprehend" KendraEndpointID = "kendra" RolesAnywhereEndpointID = "rolesanywhere" Route53DomainsEndpointID = "route53domains" From 0d876cb9c7d736034cf389e5e9c51e847a2a447b Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 19 Jul 2022 17:21:29 -0700 Subject: [PATCH 07/29] Waits for model to be trained --- internal/service/comprehend/entity_recognizer.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 0423f7801de..6fbe614fa49 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -343,7 +343,6 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { return diag.Errorf("setting tags: %s", err) } @@ -408,8 +407,8 @@ func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{}, - Target: enum.Slice(types.ModelStatusSubmitted), + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining), + Target: enum.Slice(types.ModelStatusTrained), Refresh: statusEntityRecognizer(ctx, conn, id), Timeout: timeout, } From e58089cc8f6d9d97b87d367f7d1204cee73e6e11 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 19 Jul 2022 17:35:24 -0700 Subject: [PATCH 08/29] Adds missing documentation --- website/docs/r/comprehend_entity_recognizer.html.markdown | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/docs/r/comprehend_entity_recognizer.html.markdown b/website/docs/r/comprehend_entity_recognizer.html.markdown index ed521632c15..2e72cbcfc58 100644 --- a/website/docs/r/comprehend_entity_recognizer.html.markdown +++ b/website/docs/r/comprehend_entity_recognizer.html.markdown @@ -74,6 +74,9 @@ The following arguments are optional: Each version must have a unique name within the Entity Recognizer. Has a maximum length of 63 characters. Can contain upper- and lower-case letters, numbers, and hypen (`-`). +* `volume_kms_key_id` - (Optional) ID or ARN of a KMS Key used to encrypt storage volumes during job processing. +* `vpc_config` - (Optional) Configuration parameters for VPC to contain Entity Recognizer resources. + See the [`vpc_config` Configuration Block](#vpc_config-configuration-block) section below. ### `input_data_config` Configuration Block @@ -127,6 +130,11 @@ The following arguments are optional: * `type` - (Required) An entity type to be matched by the Entity Recognizer. Cannot contain a newline (`\n`), carriage return (`\r`), or tab (`\t`). +### `vpc_config` Configuration Block + +* `security_group_ids` - (Required) List of security group IDs. +* `subnets` - (Required) List of VPC subnets. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From a4191e1251978e7298c05def4ed9c62917b58889 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 20 Jul 2022 16:22:46 -0700 Subject: [PATCH 09/29] Removes `model_policy` from Entity Recognizer --- .../service/comprehend/entity_recognizer.go | 23 ------------------- ...comprehend_entity_recognizer.html.markdown | 1 - 2 files changed, 24 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 6fbe614fa49..2bf22db998e 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -179,12 +179,6 @@ func ResourceEntityRecognizer() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "model_policy": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, - }, "name": { Type: schema.TypeString, Required: true, @@ -242,10 +236,6 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, in.ModelKmsKeyId = aws.String(v) } - if v, ok := d.Get("model_policy").(string); ok && v != "" { - in.ModelPolicy = aws.String(v) - } - if v, ok := d.Get("version_name").(string); ok && v != "" { in.VersionName = aws.String(v) } @@ -321,19 +311,6 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m return diag.Errorf("setting vpc_config: %s", err) } - // TODO - // p, err := verify.SecondJSONUnlessEquivalent(d.Get("model_policy").(string), aws.ToString(out.ModelPolicy)) - // if err != nil { - // return diag.Errorf("while setting model_policy (%s), encountered: %s", p, err) - // } - - // p, err = structure.NormalizeJsonString(p) - // if err != nil { - // return diag.Errorf("policy (%s) is invalid JSON: %s", p, err) - // } - - // d.Set("model_policy", p) - tags, err := ListTags(ctx, conn, d.Id()) if err != nil { return diag.Errorf("listing tags for Comprehend Entity Recognizer (%s): %s", d.Id(), err) diff --git a/website/docs/r/comprehend_entity_recognizer.html.markdown b/website/docs/r/comprehend_entity_recognizer.html.markdown index 2e72cbcfc58..0522e83029f 100644 --- a/website/docs/r/comprehend_entity_recognizer.html.markdown +++ b/website/docs/r/comprehend_entity_recognizer.html.markdown @@ -68,7 +68,6 @@ The following arguments are required: The following arguments are optional: * `model_kms_key_id` - (Optional) The ID or ARN of a KMS Key used to encrypt trained Entity Recognizers. -* `model_policy` - (Optional) Resource-based IAM policy encoded in JSON, used to [share the Entity Recognizer](https://docs.aws.amazon.com/comprehend/latest/dg/custom-copy-sharing.html#custom-copy-sharing-example-policy). * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` Configuration Block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `version_name` - (Optional) Name for the version of the Entity Recognizer. Each version must have a unique name within the Entity Recognizer. From d1d3e91d942eb7926aae4f3005e32111fa55597f Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 20 Jul 2022 18:05:43 -0700 Subject: [PATCH 10/29] Adds tag support --- .../service/comprehend/entity_recognizer.go | 12 +- .../comprehend/entity_recognizer_test.go | 332 +++++++++++++++++- 2 files changed, 327 insertions(+), 17 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 2bf22db998e..4e128e28f09 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -332,7 +332,17 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m } func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return nil + conn := meta.(*conns.AWSClient).ComprehendConn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { + return diag.Errorf("updating tags for Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + } + + return resourceEntityRecognizerRead(ctx, d, meta) } func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index f5b3e950e28..0b1e0b1f11c 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -6,6 +6,7 @@ import ( "regexp" "testing" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/comprehend/types" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -100,6 +101,128 @@ func TestAccComprehendEntityRecognizer_disappears(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v1, v2, v3 types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccEntityRecognizerConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v2), + testAccCheckEntityRecognizerNotRecreated(&v1, &v2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccEntityRecognizerConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v3), + testAccCheckEntityRecognizerNotRecreated(&v2, &v3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v1, v2, v3 types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: acctest.ConfigCompose( + acctest.ConfigDefaultTags_Tags1("providerkey1", "providervalue1"), + testAccEntityRecognizerConfig_tags0(rName), + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", "providervalue1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: acctest.ConfigCompose( + acctest.ConfigDefaultTags_Tags2("providerkey1", "providervalue1", "providerkey2", "providervalue2"), + testAccEntityRecognizerConfig_tags0(rName), + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v2), + testAccCheckEntityRecognizerNotRecreated(&v1, &v2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", "providervalue1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey2", "providervalue2"), + ), + }, + { + Config: acctest.ConfigCompose( + acctest.ConfigDefaultTags_Tags1("providerkey1", "value1"), + testAccEntityRecognizerConfig_tags0(rName), + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v3), + testAccCheckEntityRecognizerNotRecreated(&v2, &v3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", "value1"), + ), + }, + }, + }) +} + // TODO: test deletion from in-error state. Try insufficient permissions to force error // TODO: add test for catching, e.g. permission errors in training @@ -152,19 +275,34 @@ func testAccCheckEntityRecognizerExists(name string, entityrecognizer *types.Ent } } -// func testAccCheckEntityRecognizerNotRecreated(before, after *types.EntityRecognizerProperties) resource.TestCheckFunc { +// func testAccCheckEntityRecognizerRecreated(before, after *types.EntityRecognizerProperties) resource.TestCheckFunc { // return func(s *terraform.State) error { -// if before, after := aws.StringValue(before.EntityRecognizerId), aws.StringValue(after.EntityRecognizerId); before != after { -// return fmt.Errorf("Comprehend Entity Recognizer (%s/%s) recreated", before, after) +// if entityRecognizerIdentity(before, after) { +// return fmt.Errorf("Comprehend Entity Recognizer not recreated") // } // return nil // } // } +func testAccCheckEntityRecognizerNotRecreated(before, after *types.EntityRecognizerProperties) resource.TestCheckFunc { + return func(s *terraform.State) error { + if !entityRecognizerIdentity(before, after) { + return fmt.Errorf("Comprehend Entity Recognizer recreated") + } + + return nil + } +} + +func entityRecognizerIdentity(before, after *types.EntityRecognizerProperties) bool { + return aws.ToTime(before.SubmitTime).Equal(aws.ToTime(after.SubmitTime)) +} + func testAccEntityRecognizerConfig_basic(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), fmt.Sprintf(` data "aws_partition" "current" {} @@ -196,25 +334,108 @@ resource "aws_comprehend_entity_recognizer" "test" { ] } -resource "aws_s3_bucket" "test" { - bucket = %[1]q +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" } -resource "aws_s3_bucket_public_access_block" "test" { +resource "aws_s3_object" "entities" { bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true +func testAccEntityRecognizerConfig_tags0(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + tags = {} + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] } -resource "aws_s3_bucket_ownership_controls" "test" { +resource "aws_s3_object" "documents" { bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} - rule { - object_ownership = "BucketOwnerEnforced" +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + tags = { + %[2]q = %[3]q + } + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } } + + depends_on = [ + aws_iam_role_policy.test + ] } resource "aws_s3_object" "documents" { @@ -228,7 +449,86 @@ resource "aws_s3_object" "entities" { key = "entitylist.csv" source = "test-fixtures/entity_recognizer/entitylist.csv" } -`, rName)) +`, rName, tagKey1, tagValue1)) +} + +func testAccEntityRecognizerConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q +} + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccEntityRecognizerS3BucketConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "test" { + bucket = %[1]q +} + +resource "aws_s3_bucket_public_access_block" "test" { + bucket = aws_s3_bucket.test.bucket + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_ownership_controls" "test" { + bucket = aws_s3_bucket.test.bucket + + rule { + object_ownership = "BucketOwnerEnforced" + } +} +`, rName) } func testAccEntityRecognizerBasicRoleConfig(rName string) string { @@ -255,11 +555,11 @@ EOF resource "aws_iam_role_policy" "test" { role = aws_iam_role.test.name - policy = data.aws_iam_policy_document.test.json + policy = data.aws_iam_policy_document.role.json } -data "aws_iam_policy_document" "test" { +data "aws_iam_policy_document" "role" { statement { actions = [ "s3:GetObject", From 0cb5abf8a12ec3d00f764bbc6b4f213b9607b0e2 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 25 Jul 2022 11:26:18 -0700 Subject: [PATCH 11/29] Adds handling for version name --- internal/service/comprehend/consts.go | 3 + .../service/comprehend/entity_recognizer.go | 91 ++++++++-- .../comprehend/entity_recognizer_test.go | 163 +++++++++++++++++- internal/service/comprehend/validate.go | 2 +- ...comprehend_entity_recognizer.html.markdown | 2 +- 5 files changed, 237 insertions(+), 24 deletions(-) diff --git a/internal/service/comprehend/consts.go b/internal/service/comprehend/consts.go index d2bf6629ad7..0a1d641394e 100644 --- a/internal/service/comprehend/consts.go +++ b/internal/service/comprehend/consts.go @@ -5,3 +5,6 @@ import ( ) const iamPropagationTimeout = 2 * time.Minute + +// Avoid service throttling +const entityRegcognizerMinInterval = 1 * time.Second diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 4e128e28f09..e4bc8299498 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -251,7 +251,7 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, in.Tags = Tags(tags.IgnoreAWS()) } - // Because the IAM credentials aren't evaluated until later, we need to ensure we wait for the IAM propagation delay + // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay time.Sleep(iamPropagationTimeout) out, err := conn.CreateEntityRecognizer(ctx, in) @@ -299,8 +299,12 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.Errorf("reading Comprehend Entity Recognizer (%s): %s", d.Id(), err) } - re := regexp.MustCompile(`entity-recognizer/(.*)`) - name := re.FindStringSubmatch(arn.Resource)[1] + re := regexp.MustCompile(`^entity-recognizer/([[:alnum:]-]+)`) + matches := re.FindStringSubmatch(arn.Resource) + if len(matches) != 2 { + return diag.Errorf("reading Comprehend Entity Recognizer (%s): unable to parse ARN (%s)", d.Id(), aws.ToString(out.EntityRecognizerArn)) + } + name := matches[1] d.Set("name", name) if err := d.Set("input_data_config", flattenInputDataConfig(out.InputDataConfig)); err != nil { @@ -334,7 +338,53 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).ComprehendConn - if d.HasChange("tags_all") { + if d.HasChangesExcept("tags", "tags_all") { + in := &comprehend.CreateEntityRecognizerInput{ + DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), + InputDataConfig: expandInputDataConfig(d.Get("input_data_config").([]interface{})), + LanguageCode: types.LanguageCode(d.Get("language_code").(string)), + RecognizerName: aws.String(d.Get("name").(string)), + VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), + ClientRequestToken: aws.String(resource.UniqueId()), + } + + if v, ok := d.Get("model_kms_key_id").(string); ok && v != "" { + in.ModelKmsKeyId = aws.String(v) + } + + if v, ok := d.Get("version_name").(string); ok && v != "" { + in.VersionName = aws.String(v) + } + + if v, ok := d.Get("volume_kms_key_id").(string); ok && v != "" { + in.VolumeKmsKeyId = aws.String(v) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } + + // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay + time.Sleep(iamPropagationTimeout) + + out, err := conn.CreateEntityRecognizer(ctx, in) + if err != nil { + return diag.Errorf("updating Amazon Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } + + if out == nil || out.EntityRecognizerArn == nil { + return diag.Errorf("updating Amazon Comprehend Entity Recognizer (%s): empty output", d.Id()) + } + + d.SetId(aws.ToString(out.EntityRecognizerArn)) + + if _, err := waitEntityRecognizerCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for Amazon Comprehend Entity Recognizer (%s) to be updated: %s", d.Id(), err) + } + } else if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { @@ -394,14 +444,21 @@ func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining), - Target: enum.Slice(types.ModelStatusTrained), - Refresh: statusEntityRecognizer(ctx, conn, id), - Timeout: timeout, + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining), + Target: enum.Slice(types.ModelStatusTrained), + Refresh: statusEntityRecognizer(ctx, conn, id), + MinTimeout: entityRegcognizerMinInterval, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) if out, ok := outputRaw.(*types.EntityRecognizerProperties); ok { + var ues *resource.UnexpectedStateError + if errors.As(err, &ues) { + if ues.State == string(types.ModelStatusInError) { + err = errors.New(aws.ToString(out.Message)) + } + } return out, err } @@ -410,10 +467,11 @@ func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, i func waitEntityRecognizerStopped(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusStopRequested), - Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError), - Refresh: statusEntityRecognizer(ctx, conn, id), - Timeout: timeout, + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusStopRequested), + Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError), + Refresh: statusEntityRecognizer(ctx, conn, id), + MinTimeout: entityRegcognizerMinInterval, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -426,10 +484,11 @@ func waitEntityRecognizerStopped(ctx context.Context, conn *comprehend.Client, i func waitEntityRecognizerDeleted(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusInError, types.ModelStatusStopRequested), - Target: []string{}, - Refresh: statusEntityRecognizer(ctx, conn, id), - Timeout: timeout, + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusInError, types.ModelStatusStopRequested), + Target: []string{}, + Refresh: statusEntityRecognizer(ctx, conn, id), + MinTimeout: entityRegcognizerMinInterval, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 0b1e0b1f11c..f83eba6f1e3 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/comprehend" "github.com/aws/aws-sdk-go-v2/service/comprehend/types" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -41,9 +42,10 @@ func TestAccComprehendEntityRecognizer_basic(t *testing.T) { Config: testAccEntityRecognizerConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s`, rName))), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s$`, rName))), resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.annotations.#", "0"), @@ -101,6 +103,64 @@ func TestAccComprehendEntityRecognizer_disappears(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_VersionName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_versionName(rName, vName1, "key", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version_name", vName1), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, vName1))), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckNoResourceAttr(resourceName, "model_policy"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccEntityRecognizerConfig_versionName(rName, vName2, "key", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 2), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version_name", vName2), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, vName2))), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckNoResourceAttr(resourceName, "model_policy"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key", "value2"), + ), + }, + }, + }) +} + func TestAccComprehendEntityRecognizer_tags(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -124,6 +184,7 @@ func TestAccComprehendEntityRecognizer_tags(t *testing.T) { Config: testAccEntityRecognizerConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v1), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), @@ -138,6 +199,7 @@ func TestAccComprehendEntityRecognizer_tags(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v2), testAccCheckEntityRecognizerNotRecreated(&v1, &v2), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -148,6 +210,7 @@ func TestAccComprehendEntityRecognizer_tags(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v3), testAccCheckEntityRecognizerNotRecreated(&v2, &v3), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), @@ -182,6 +245,7 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v1), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", "providervalue1"), @@ -200,6 +264,7 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v2), testAccCheckEntityRecognizerNotRecreated(&v1, &v2), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", "providervalue1"), @@ -214,6 +279,7 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v3), testAccCheckEntityRecognizerNotRecreated(&v2, &v3), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", "value1"), @@ -299,6 +365,38 @@ func entityRecognizerIdentity(before, after *types.EntityRecognizerProperties) b return aws.ToTime(before.SubmitTime).Equal(aws.ToTime(after.SubmitTime)) } +func testAccCheckEntityRecognizerPublishedVersions(name string, count int) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Comprehend Entity Recognizer is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn + ctx := context.Background() + + input := &comprehend.ListEntityRecognizersInput{ + Filter: &types.EntityRecognizerFilter{ + RecognizerName: aws.String(rs.Primary.Attributes["name"]), + }, + } + total := 0 + paginator := comprehend.NewListEntityRecognizersPaginator(conn, input) + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + return err + } + total += len(output.EntityRecognizerPropertiesList) + } + return nil + } +} + func testAccEntityRecognizerConfig_basic(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), @@ -348,6 +446,60 @@ resource "aws_s3_object" "entities" { `, rName)) } +func testAccEntityRecognizerConfig_versionName(rName, vName, key, value string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + version_name = %[2]q + + data_access_role_arn = aws_iam_role.test.arn + + tags = { + %[3]q = %[4]q + } + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName, vName, key, value)) +} + func testAccEntityRecognizerConfig_tags0(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), @@ -412,7 +564,7 @@ resource "aws_comprehend_entity_recognizer" "test" { data_access_role_arn = aws_iam_role.test.arn tags = { - %[2]q = %[3]q + %[2]q = %[3]q } language_code = "en" @@ -465,9 +617,9 @@ resource "aws_comprehend_entity_recognizer" "test" { data_access_role_arn = aws_iam_role.test.arn tags = { - %[2]q = %[3]q - %[4]q = %[5]q -} + %[2]q = %[3]q + %[4]q = %[5]q + } language_code = "en" input_data_config { @@ -558,7 +710,6 @@ resource "aws_iam_role_policy" "test" { policy = data.aws_iam_policy_document.role.json } - data "aws_iam_policy_document" "role" { statement { actions = [ diff --git a/internal/service/comprehend/validate.go b/internal/service/comprehend/validate.go index 4def55e6c9e..ad5e47175c1 100644 --- a/internal/service/comprehend/validate.go +++ b/internal/service/comprehend/validate.go @@ -15,5 +15,5 @@ var validModelVersionName = validIdentifier var validIdentifier = validation.All( validation.StringLenBetween(1, modelIdentifierMaxLen), - validation.StringMatch(regexp.MustCompile(`[[:alnum:]-]`), "can contain A-Z, a-z, 0-9, and hypen (-)"), + validation.StringMatch(regexp.MustCompile(`[[:alnum:]-]`), "must contain A-Z, a-z, 0-9, and hypen (-)"), ) diff --git a/website/docs/r/comprehend_entity_recognizer.html.markdown b/website/docs/r/comprehend_entity_recognizer.html.markdown index 0522e83029f..58347563f59 100644 --- a/website/docs/r/comprehend_entity_recognizer.html.markdown +++ b/website/docs/r/comprehend_entity_recognizer.html.markdown @@ -138,7 +138,7 @@ The following arguments are optional: In addition to all arguments above, the following attributes are exported: -* `arn` - ARN of the Entity Recognizer. +* `arn` - ARN of the Entity Recognizer version. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Timeouts From fc63f151a97b0c84400f92547f9a554af963c92f Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 27 Jul 2022 14:41:09 -0700 Subject: [PATCH 12/29] Adds `PKG` parameter for `make test` --- GNUmakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/GNUmakefile b/GNUmakefile index d2b950e28a6..4c0f09c33d0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -8,6 +8,7 @@ ACCTEST_PARALLELISM ?= 20 ifneq ($(origin PKG), undefined) PKG_NAME = internal/service/$(PKG) + TEST = ./$(PKG_NAME)/... endif ifneq ($(origin TESTS), undefined) From 9d518f3cc657494225f72e0a71a774567bb21753 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 27 Jul 2022 14:44:19 -0700 Subject: [PATCH 13/29] Adds support for KMS keys --- internal/service/comprehend/diff.go | 51 ++ internal/service/comprehend/diff_test.go | 68 +++ .../service/comprehend/entity_recognizer.go | 12 +- .../comprehend/entity_recognizer_test.go | 528 ++++++++++++++++++ internal/service/comprehend/validate.go | 40 ++ internal/service/comprehend/validate_test.go | 51 ++ 6 files changed, 746 insertions(+), 4 deletions(-) create mode 100644 internal/service/comprehend/diff.go create mode 100644 internal/service/comprehend/diff_test.go create mode 100644 internal/service/comprehend/validate_test.go diff --git a/internal/service/comprehend/diff.go b/internal/service/comprehend/diff.go new file mode 100644 index 00000000000..014395c9c39 --- /dev/null +++ b/internal/service/comprehend/diff.go @@ -0,0 +1,51 @@ +package comprehend + +import ( + "regexp" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func diffSuppressKMSKeyId(k, oldValue, newValue string, d *schema.ResourceData) bool { + if oldValue == newValue { + return true + } + + oldId := oldValue + if arn.IsARN(oldValue) { + oldId = kmsKeyIdFromARN(oldValue) + } + + newId := newValue + if arn.IsARN(newValue) { + newId = kmsKeyIdFromARN(newValue) + } + + if oldId == newId { + return true + } + + return false +} + +func kmsKeyIdFromARN(s string) string { + arn, err := arn.Parse(s) + if err != nil { + return "" + } + + return kmsKeyIdFromARNResource(arn.Resource) +} + +func kmsKeyIdFromARNResource(s string) string { + re := regexp.MustCompile(`^key/(` + verify.UUIDRegexPattern + ")$") + matches := re.FindStringSubmatch(s) + if matches == nil || len(matches) != 2 { + return "" + } + + return matches[1] + +} diff --git a/internal/service/comprehend/diff_test.go b/internal/service/comprehend/diff_test.go new file mode 100644 index 00000000000..5bbad568065 --- /dev/null +++ b/internal/service/comprehend/diff_test.go @@ -0,0 +1,68 @@ +package comprehend + +import ( + "testing" +) + +func TestDiffSuppressKMSKeyId(t *testing.T) { + testcases := map[string]struct { + old string + new string + expectSuppress bool + }{ + "same ids": { + old: "57ff7a43-341d-46b6-aee3-a450c9de6dc8", + new: "57ff7a43-341d-46b6-aee3-a450c9de6dc8", + expectSuppress: true, + }, + "same arns": { + old: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + new: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + expectSuppress: true, + }, + + "different ids": { + old: "57ff7a43-341d-46b6-aee3-a450c9de6dc8", + new: "2fc6ef7b-7a71-4426-ad52-0840169767f1", + expectSuppress: false, + }, + "different arns": { + old: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + new: "arn:aws:kms:us-west-2:123456789012:key/2fc6ef7b-7a71-4426-ad52-0840169767f1", // lintignore:AWSAT003,AWSAT005 + expectSuppress: false, + }, + + "id to equivalent arn": { + old: "57ff7a43-341d-46b6-aee3-a450c9de6dc8", + new: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + expectSuppress: true, + }, + "arn to equivalent id": { + old: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + new: "57ff7a43-341d-46b6-aee3-a450c9de6dc8", + expectSuppress: true, + }, + + "id to different arn": { + old: "57ff7a43-341d-46b6-aee3-a450c9de6dc8", + new: "arn:aws:kms:us-west-2:123456789012:key/2fc6ef7b-7a71-4426-ad52-0840169767f1", // lintignore:AWSAT003,AWSAT005 + expectSuppress: false, + }, + "arn to different id": { + old: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + new: "2fc6ef7b-7a71-4426-ad52-0840169767f1", + expectSuppress: false, + }, + } + + for name, testcase := range testcases { + t.Run(name, func(t *testing.T) { + actual := diffSuppressKMSKeyId("field", testcase.old, testcase.new, nil) + + if e := testcase.expectSuppress; actual != e { + t.Fatalf("expected %t, got %t", e, actual) + } + }) + } + +} diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index e4bc8299498..b27e122477c 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -176,8 +176,10 @@ func ResourceEntityRecognizer() *schema.Resource { ValidateDiagFunc: enum.Validate[types.SyntaxLanguageCode](), }, "model_kms_key_id": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: diffSuppressKMSKeyId, + ValidateFunc: validateKMSKey, }, "name": { Type: schema.TypeString, @@ -192,8 +194,10 @@ func ResourceEntityRecognizer() *schema.Resource { ValidateFunc: validModelVersionName, }, "volume_kms_key_id": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: diffSuppressKMSKeyId, + ValidateFunc: validateKMSKey, }, "vpc_config": { Type: schema.TypeList, diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index f83eba6f1e3..e42ecc16a7c 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -161,6 +161,137 @@ func TestAccComprehendEntityRecognizer_VersionName(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_KMSKeys_CreateIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_kmsKeyIds(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttrPair(resourceName, "model_kms_key_id", "aws_kms_key.model", "key_id"), + resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume", "key_id"), + ), + }, + { + Config: testAccEntityRecognizerConfig_kmsKeyARNs(rName), + PlanOnly: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_KMSKeys_CreateARNs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_kmsKeyARNs(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttrPair(resourceName, "model_kms_key_id", "aws_kms_key.model", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume", "arn"), + ), + }, + { + Config: testAccEntityRecognizerConfig_kmsKeyIds(rName), + PlanOnly: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_KMSKeys_Update(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v1, v2, v3, v4 types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_kmsKeys_None(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v1), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), + ), + }, + { + Config: testAccEntityRecognizerConfig_kmsKeys_Set(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v2), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 2), + resource.TestCheckResourceAttrPair(resourceName, "model_kms_key_id", "aws_kms_key.model", "key_id"), + resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume", "key_id"), + ), + }, + { + Config: testAccEntityRecognizerConfig_kmsKeys_Update(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v3), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 3), + resource.TestCheckResourceAttrPair(resourceName, "model_kms_key_id", "aws_kms_key.model2", "key_id"), + resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume2", "key_id"), + ), + }, + { + Config: testAccEntityRecognizerConfig_kmsKeys_None(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &v4), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 4), + resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), + ), + }, + }, + }) +} + func TestAccComprehendEntityRecognizer_tags(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -500,6 +631,403 @@ resource "aws_s3_object" "entities" { `, rName, vName, key, value)) } +func testAccEntityRecognizerConfig_kmsKeyIds(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + model_kms_key_id = aws_kms_key.model.key_id + volume_kms_key_id = aws_kms_key.volume.key_id + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_iam_role_policy" "kms_keys" { + role = aws_iam_role.test.name + + policy = data.aws_iam_policy_document.kms_keys.json +} + +data "aws_iam_policy_document" "kms_keys" { + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.model.arn, + ] + } + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.volume.arn, + ] + } +} + +resource "aws_kms_key" "model" { + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "volume" { + deletion_window_in_days = 7 +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_kmsKeyARNs(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + model_kms_key_id = aws_kms_key.model.arn + volume_kms_key_id = aws_kms_key.volume.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_iam_role_policy" "kms_keys" { + role = aws_iam_role.test.name + + policy = data.aws_iam_policy_document.kms_keys.json +} + +data "aws_iam_policy_document" "kms_keys" { + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.model.arn, + ] + } + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.volume.arn, + ] + } +} + +resource "aws_kms_key" "model" { + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "volume" { + deletion_window_in_days = 7 +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_kmsKeys_None(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_kmsKeys_Set(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + model_kms_key_id = aws_kms_key.model.key_id + volume_kms_key_id = aws_kms_key.volume.key_id + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_iam_role_policy" "kms_keys" { + role = aws_iam_role.test.name + + policy = data.aws_iam_policy_document.kms_keys.json +} + +data "aws_iam_policy_document" "kms_keys" { + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.model.arn, + ] + } + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.volume.arn, + ] + } +} + +resource "aws_kms_key" "model" { + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "volume" { + deletion_window_in_days = 7 +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_kmsKeys_Update(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + model_kms_key_id = aws_kms_key.model2.key_id + volume_kms_key_id = aws_kms_key.volume2.key_id + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_iam_role_policy" "kms_keys" { + role = aws_iam_role.test.name + + policy = data.aws_iam_policy_document.kms_keys.json +} + +data "aws_iam_policy_document" "kms_keys" { + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.model2.arn, + ] + } + statement { + actions = [ + "*", + ] + + resources = [ + aws_kms_key.volume2.arn, + ] + } +} + +resource "aws_kms_key" "model2" { + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "volume2" { + deletion_window_in_days = 7 +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + func testAccEntityRecognizerConfig_tags0(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), diff --git a/internal/service/comprehend/validate.go b/internal/service/comprehend/validate.go index ad5e47175c1..a7a89ba8e12 100644 --- a/internal/service/comprehend/validate.go +++ b/internal/service/comprehend/validate.go @@ -1,9 +1,12 @@ package comprehend import ( + "fmt" "regexp" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/verify" ) const ( @@ -17,3 +20,40 @@ var validIdentifier = validation.All( validation.StringLenBetween(1, modelIdentifierMaxLen), validation.StringMatch(regexp.MustCompile(`[[:alnum:]-]`), "must contain A-Z, a-z, 0-9, and hypen (-)"), ) + +var validateKMSKey = validation.Any( + validateKMSKeyId, + validateKMSKeyARN, +) + +var validateKMSKeyId = validation.StringMatch(regexp.MustCompile("^"+verify.UUIDRegexPattern+"$"), "must be a KMS Key ID") + +func validateKMSKeyARN(v any, k string) (ws []string, errors []error) { + value, ok := v.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if value == "" { + return + } + + parsedARN, err := arn.Parse(value) + if err != nil { + errors = append(errors, fmt.Errorf("%q (%s) is an invalid ARN: %s", k, value, err)) + return + } + + if parsedARN.Service != "kms" { + errors = append(errors, fmt.Errorf("%q (%s) is not a valid KMS Key ARN: %s", k, value, err)) + return + } + + if id := kmsKeyIdFromARNResource(parsedARN.Resource); id == "" { + errors = append(errors, fmt.Errorf("%q (%s) is not a valid KMS Key ARN: %s", k, value, err)) + return + } + + return +} diff --git a/internal/service/comprehend/validate_test.go b/internal/service/comprehend/validate_test.go new file mode 100644 index 00000000000..a7d3430e038 --- /dev/null +++ b/internal/service/comprehend/validate_test.go @@ -0,0 +1,51 @@ +package comprehend + +import ( + "testing" +) + +func TestValidateKMSKeyARN(t *testing.T) { + testcases := map[string]struct { + in any + valid bool + }{ + "kms key id": { + in: "arn:aws:kms:us-west-2:123456789012:key/57ff7a43-341d-46b6-aee3-a450c9de6dc8", // lintignore:AWSAT003,AWSAT005 + valid: true, + }, + "kms non-key id": { + in: "arn:aws:kms:us-west-2:123456789012:something/else", // lintignore:AWSAT003,AWSAT005 + valid: false, + }, + "non-kms arn": { + in: "arn:aws:iam::123456789012:user/David", // lintignore:AWSAT005 + valid: false, + }, + "not an arn": { + in: "not an arn", + valid: false, + }, + "not a string": { + in: 123, + valid: false, + }, + } + + for name, testcase := range testcases { + t.Run(name, func(t *testing.T) { + aWs, aEs := validateKMSKeyARN(testcase.in, "field") + if len(aWs) != 0 { + t.Errorf("expected no warnings, got %v", aWs) + } + if testcase.valid { + if len(aEs) != 0 { + t.Errorf("expected no errors, got %v", aEs) + } + } else { + if len(aEs) == 0 { + t.Error("expected errors, got none") + } + } + }) + } +} From 7d935d8067d26a9cef26ff50d8a802c4d66b519a Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 29 Jul 2022 15:00:21 -0700 Subject: [PATCH 14/29] Handles version naming and updating KMS keys --- docs/resource-name-generation.md | 6 +- internal/service/comprehend/consts.go | 3 +- .../service/comprehend/entity_recognizer.go | 237 ++++++++---- .../comprehend/entity_recognizer_test.go | 342 +++++++++++++++++- internal/service/comprehend/validate.go | 19 +- ...comprehend_entity_recognizer.html.markdown | 7 + 6 files changed, 529 insertions(+), 85 deletions(-) diff --git a/docs/resource-name-generation.md b/docs/resource-name-generation.md index 8fd0cd6d6b2..ffede4dbf6c 100644 --- a/docs/resource-name-generation.md +++ b/docs/resource-name-generation.md @@ -65,7 +65,7 @@ func TestAccServiceThing_nameGenerated(t *testing.T) { Config: testAccThingConfig_nameGenerated(), Check: resource.ComposeTestCheckFunc( testAccCheckThingExists(resourceName, &thing), - create.TestCheckResourceAttrNameGenerated(resourceName, "name"), + acctest.CheckResourceAttrNameGenerated(resourceName, "name"), resource.TestCheckResourceAttr(resourceName, "name_prefix", resource.UniqueIdPrefix), ), }, @@ -93,7 +93,7 @@ func TestAccServiceThing_namePrefix(t *testing.T) { Config: testAccThingConfig_namePrefix("tf-acc-test-prefix-"), Check: resource.ComposeTestCheckFunc( testAccCheckThingExists(resourceName, &thing), - create.TestCheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"), + acctest.CheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"), resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-acc-test-prefix-"), ), }, @@ -156,4 +156,4 @@ d.Set("name", resp.Name) d.Set("name_prefix", create.NamePrefixFromNameWithSuffix(aws.StringValue(resp.Name), ".fifo")) ``` -There are also functions `create.TestCheckResourceAttrNameWithSuffixGenerated` and `create.TestCheckResourceAttrNameWithSuffixFromPrefix` for use in tests. +There are also functions `acctest.CheckResourceAttrNameWithSuffixGenerated` and `acctest.CheckResourceAttrNameWithSuffixFromPrefix` for use in tests. diff --git a/internal/service/comprehend/consts.go b/internal/service/comprehend/consts.go index 0a1d641394e..a087df5b0fd 100644 --- a/internal/service/comprehend/consts.go +++ b/internal/service/comprehend/consts.go @@ -7,4 +7,5 @@ import ( const iamPropagationTimeout = 2 * time.Minute // Avoid service throttling -const entityRegcognizerMinInterval = 1 * time.Second +const entityRegcognizerDelay = 10 * time.Minute +const entityRegcognizerPollInterval = 60 * time.Second diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index b27e122477c..3dbc790e16d 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -3,6 +3,7 @@ package comprehend import ( "context" "errors" + "fmt" "log" "regexp" "time" @@ -11,11 +12,13 @@ import ( "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/comprehend" "github.com/aws/aws-sdk-go-v2/service/comprehend/types" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/enum" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -189,9 +192,18 @@ func ResourceEntityRecognizer() *schema.Resource { "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), "version_name": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validModelVersionName, + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validModelVersionName, + ConflictsWith: []string{"version_name_prefix"}, + }, + "version_name_prefix": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validModelVersionNamePrefix, + ConflictsWith: []string{"version_name"}, }, "volume_kms_key_id": { Type: schema.TypeString, @@ -240,7 +252,10 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, in.ModelKmsKeyId = aws.String(v) } - if v, ok := d.Get("version_name").(string); ok && v != "" { + versionName := d.GetRawConfig().GetAttr("version_name") + if versionName.IsNull() { + in.VersionName = aws.String(create.Name("", d.Get("version_name_prefix").(string))) + } else if v := versionName.AsString(); v != "" { in.VersionName = aws.String(v) } @@ -258,7 +273,25 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay time.Sleep(iamPropagationTimeout) - out, err := conn.CreateEntityRecognizer(ctx, in) + var out *comprehend.CreateEntityRecognizerOutput + err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + var err error + out, err = conn.CreateEntityRecognizer(ctx, in) + + if err != nil { + var tmre *types.TooManyRequestsException + if errors.As(err, &tmre) { + return resource.RetryableError(err) + } else { + return resource.NonRetryableError(err) + } + } + + return nil + }) + if tfresource.TimedOut(err) { + out, err = conn.CreateEntityRecognizer(ctx, in) + } if err != nil { return diag.Errorf("creating Amazon Comprehend Entity Recognizer (%s): %s", d.Get("name").(string), err) } @@ -296,19 +329,14 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m d.Set("language_code", out.LanguageCode) d.Set("model_kms_key_id", out.ModelKmsKeyId) d.Set("version_name", out.VersionName) + d.Set("version_name_prefix", create.NamePrefixFromName(aws.ToString(out.VersionName))) d.Set("volume_kms_key_id", out.VolumeKmsKeyId) // DescribeEntityRecognizer() doesn't return the model name - arn, err := arn.Parse(aws.ToString(out.EntityRecognizerArn)) + name, err := EntityRecognizerParseARN(aws.ToString(out.EntityRecognizerArn)) if err != nil { return diag.Errorf("reading Comprehend Entity Recognizer (%s): %s", d.Id(), err) } - re := regexp.MustCompile(`^entity-recognizer/([[:alnum:]-]+)`) - matches := re.FindStringSubmatch(arn.Resource) - if len(matches) != 2 { - return diag.Errorf("reading Comprehend Entity Recognizer (%s): unable to parse ARN (%s)", d.Id(), aws.ToString(out.EntityRecognizerArn)) - } - name := matches[1] d.Set("name", name) if err := d.Set("input_data_config", flattenInputDataConfig(out.InputDataConfig)); err != nil { @@ -356,8 +384,11 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, in.ModelKmsKeyId = aws.String(v) } - if v, ok := d.Get("version_name").(string); ok && v != "" { - in.VersionName = aws.String(v) + if d.HasChange("version_name") { + in.VersionName = aws.String(d.Get("version_name").(string)) + } else if v := d.Get("version_name_prefix").(string); v != "" { + versionName := create.Name("", d.Get("version_name_prefix").(string)) + in.VersionName = aws.String(versionName) } if v, ok := d.Get("volume_kms_key_id").(string); ok && v != "" { @@ -374,7 +405,25 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay time.Sleep(iamPropagationTimeout) - out, err := conn.CreateEntityRecognizer(ctx, in) + var out *comprehend.CreateEntityRecognizerOutput + err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + var err error + out, err = conn.CreateEntityRecognizer(ctx, in) + + if err != nil { + var tmre *types.TooManyRequestsException + if errors.As(err, &tmre) { + return resource.RetryableError(err) + } else { + return resource.NonRetryableError(err) + } + } + + return nil + }) + if tfresource.TimedOut(err) { + out, err = conn.CreateEntityRecognizer(ctx, in) + } if err != nil { return diag.Errorf("updating Amazon Comprehend Entity Recognizer (%s): %s", d.Id(), err) } @@ -422,37 +471,103 @@ func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, return nil } - return diag.Errorf("waiting for Comprehend Entity Recognizer (%s) to be deleted: %s", d.Id(), err) + return diag.Errorf("waiting for Comprehend Entity Recognizer (%s) to be stopped: %s", d.Id(), err) } - log.Printf("[INFO] Deleting Comprehend Entity Recognizer (%s)", d.Id()) + name, err := EntityRecognizerParseARN(d.Id()) + if err != nil { + return diag.Errorf("deleting Comprehend Entity Recognizer (%s): %s", d.Id(), err) + } - _, err = conn.DeleteEntityRecognizer(ctx, &comprehend.DeleteEntityRecognizerInput{ - EntityRecognizerArn: aws.String(d.Id()), - }) + log.Printf("[INFO] Deleting Comprehend Entity Recognizer (%s)", name) + + versions, err := ListEntityRecognizerVersionsByName(ctx, conn, name) + if err != nil { + return diag.Errorf("deleting Comprehend Entity Recognizer (%s): %s", name, err) + } + + var g multierror.Group + for _, v := range versions { + v := v + g.Go(func() error { + _, err = conn.DeleteEntityRecognizer(ctx, &comprehend.DeleteEntityRecognizerInput{ + EntityRecognizerArn: v.EntityRecognizerArn, + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if !errors.As(err, &nfe) { + return fmt.Errorf("deleting version (%s): %w", aws.ToString(v.VersionName), err) + } + } + + if _, err := waitEntityRecognizerDeleted(ctx, conn, aws.ToString(v.EntityRecognizerArn), d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("waiting for version (%s) to be deleted: %s", aws.ToString(v.VersionName), err) + } + + return nil + }) + } + + if err := g.Wait(); err != nil { + return diag.Errorf("deleting Comprehend Entity Recognizer (%s): %s", name, err) + } + + return nil +} + +func FindEntityRecognizerByID(ctx context.Context, conn *comprehend.Client, id string) (*types.EntityRecognizerProperties, error) { + in := &comprehend.DescribeEntityRecognizerInput{ + EntityRecognizerArn: aws.String(id), + } + + out, err := conn.DescribeEntityRecognizer(ctx, in) if err != nil { var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { - return nil + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } } - return diag.Errorf("deleting Comprehend Entity Recognizer (%s): %s", d.Id(), err) + return nil, err } - if _, err := waitEntityRecognizerDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return diag.Errorf("waiting for Comprehend Entity Recognizer (%s) to be deleted: %s", d.Id(), err) + if out == nil || out.EntityRecognizerProperties == nil { + return nil, tfresource.NewEmptyResultError(in) } - return nil + return out.EntityRecognizerProperties, nil +} + +func ListEntityRecognizerVersionsByName(ctx context.Context, conn *comprehend.Client, name string) ([]types.EntityRecognizerProperties, error) { + results := []types.EntityRecognizerProperties{} + + input := &comprehend.ListEntityRecognizersInput{ + Filter: &types.EntityRecognizerFilter{ + RecognizerName: aws.String(name), + }, + } + paginator := comprehend.NewListEntityRecognizersPaginator(conn, input) + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + return []types.EntityRecognizerProperties{}, err + } + results = append(results, output.EntityRecognizerPropertiesList...) + } + + return results, nil } func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining), - Target: enum.Slice(types.ModelStatusTrained), - Refresh: statusEntityRecognizer(ctx, conn, id), - MinTimeout: entityRegcognizerMinInterval, - Timeout: timeout, + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining), + Target: enum.Slice(types.ModelStatusTrained), + Refresh: statusEntityRecognizer(ctx, conn, id), + Delay: entityRegcognizerDelay, + PollInterval: entityRegcognizerPollInterval, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -471,11 +586,11 @@ func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, i func waitEntityRecognizerStopped(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusStopRequested), - Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError), - Refresh: statusEntityRecognizer(ctx, conn, id), - MinTimeout: entityRegcognizerMinInterval, - Timeout: timeout, + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusStopRequested), + Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError), + Refresh: statusEntityRecognizer(ctx, conn, id), + PollInterval: entityRegcognizerPollInterval, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -488,11 +603,13 @@ func waitEntityRecognizerStopped(ctx context.Context, conn *comprehend.Client, i func waitEntityRecognizerDeleted(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusInError, types.ModelStatusStopRequested), - Target: []string{}, - Refresh: statusEntityRecognizer(ctx, conn, id), - MinTimeout: entityRegcognizerMinInterval, - Timeout: timeout, + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusInError, types.ModelStatusStopRequested), + Target: []string{}, + Refresh: statusEntityRecognizer(ctx, conn, id), + Delay: entityRegcognizerDelay, + PollInterval: entityRegcognizerPollInterval, + NotFoundChecks: 3, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -518,31 +635,6 @@ func statusEntityRecognizer(ctx context.Context, conn *comprehend.Client, id str } } -func FindEntityRecognizerByID(ctx context.Context, conn *comprehend.Client, id string) (*types.EntityRecognizerProperties, error) { - in := &comprehend.DescribeEntityRecognizerInput{ - EntityRecognizerArn: aws.String(id), - } - - out, err := conn.DescribeEntityRecognizer(ctx, in) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - return nil, err - } - - if out == nil || out.EntityRecognizerProperties == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out.EntityRecognizerProperties, nil -} - func flattenInputDataConfig(apiObject *types.EntityRecognizerInputDataConfig) []interface{} { if apiObject == nil { return nil @@ -852,3 +944,18 @@ func expandVPCConfig(tfList []interface{}) *types.VpcConfig { return a } + +func EntityRecognizerParseARN(arnString string) (string, error) { + arn, err := arn.Parse(arnString) + if err != nil { + return "", err + } + re := regexp.MustCompile(`^entity-recognizer/([[:alnum:]-]+)`) + matches := re.FindStringSubmatch(arn.Resource) + if len(matches) != 2 { + return "", fmt.Errorf("unable to parse %q", arnString) + } + name := matches[1] + + return name, nil +} diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index e42ecc16a7c..1964e33ee82 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfcomprehend "github.com/hashicorp/terraform-provider-aws/internal/service/comprehend" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -45,7 +44,7 @@ func TestAccComprehendEntityRecognizer_basic(t *testing.T) { testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s$`, rName))), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, uniqueIDPattern()))), resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.annotations.#", "0"), @@ -58,7 +57,8 @@ func TestAccComprehendEntityRecognizer_basic(t *testing.T) { resource.TestCheckNoResourceAttr(resourceName, "model_policy"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), - resource.TestCheckResourceAttr(resourceName, "version_name", ""), + acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", resource.UniqueIdPrefix), resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), ), @@ -103,7 +103,7 @@ func TestAccComprehendEntityRecognizer_disappears(t *testing.T) { }) } -func TestAccComprehendEntityRecognizer_VersionName(t *testing.T) { +func TestAccComprehendEntityRecognizer_versionName(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") } @@ -131,6 +131,7 @@ func TestAccComprehendEntityRecognizer_VersionName(t *testing.T) { testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "version_name", vName1), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", ""), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, vName1))), resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), resource.TestCheckNoResourceAttr(resourceName, "model_policy"), @@ -150,6 +151,7 @@ func TestAccComprehendEntityRecognizer_VersionName(t *testing.T) { testAccCheckEntityRecognizerPublishedVersions(resourceName, 2), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "version_name", vName2), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", ""), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, vName2))), resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), resource.TestCheckNoResourceAttr(resourceName, "model_policy"), @@ -161,6 +163,123 @@ func TestAccComprehendEntityRecognizer_VersionName(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_versionNameEmpty(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_versionNameEmpty(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version_name", ""), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", ""), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s$`, rName))), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckNoResourceAttr(resourceName, "model_policy"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_versionNameGenerated(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_versionNameGenerated(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", resource.UniqueIdPrefix), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, uniqueIDPattern()))), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_versionNamePrefix(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_versioNamePrefix(rName, "tf-acc-test-prefix-"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + acctest.CheckResourceAttrNameFromPrefix(resourceName, "version_name", "tf-acc-test-prefix-"), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", "tf-acc-test-prefix-"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, prefixedUniqueIDPattern("tf-acc-test-prefix-")))), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComprehendEntityRecognizer_KMSKeys_CreateIDs(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -189,6 +308,11 @@ func TestAccComprehendEntityRecognizer_KMSKeys_CreateIDs(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume", "key_id"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccEntityRecognizerConfig_kmsKeyARNs(rName), PlanOnly: true, @@ -225,6 +349,11 @@ func TestAccComprehendEntityRecognizer_KMSKeys_CreateARNs(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume", "arn"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccEntityRecognizerConfig_kmsKeyIds(rName), PlanOnly: true, @@ -270,6 +399,11 @@ func TestAccComprehendEntityRecognizer_KMSKeys_Update(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume", "key_id"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccEntityRecognizerConfig_kmsKeys_Update(rName), Check: resource.ComposeAggregateTestCheckFunc( @@ -279,6 +413,11 @@ func TestAccComprehendEntityRecognizer_KMSKeys_Update(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "volume_kms_key_id", "aws_kms_key.volume2", "key_id"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccEntityRecognizerConfig_kmsKeys_None(rName), Check: resource.ComposeAggregateTestCheckFunc( @@ -374,7 +513,7 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { acctest.ConfigDefaultTags_Tags1("providerkey1", "providervalue1"), testAccEntityRecognizerConfig_tags0(rName), ), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v1), testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -392,7 +531,7 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { acctest.ConfigDefaultTags_Tags2("providerkey1", "providervalue1", "providerkey2", "providervalue2"), testAccEntityRecognizerConfig_tags0(rName), ), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v2), testAccCheckEntityRecognizerNotRecreated(&v1, &v2), testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), @@ -407,7 +546,7 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { acctest.ConfigDefaultTags_Tags1("providerkey1", "value1"), testAccEntityRecognizerConfig_tags0(rName), ), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &v3), testAccCheckEntityRecognizerNotRecreated(&v2, &v3), testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), @@ -433,15 +572,30 @@ func testAccCheckEntityRecognizerDestroy(s *terraform.State) error { continue } - _, err := tfcomprehend.FindEntityRecognizerByID(ctx, conn, rs.Primary.ID) + name, err := tfcomprehend.EntityRecognizerParseARN(rs.Primary.ID) if err != nil { - if tfresource.NotFound(err) { - return nil - } return err } - return fmt.Errorf("Expected Comprehend Entity Recognizer to be destroyed, %s found", rs.Primary.ID) + input := &comprehend.ListEntityRecognizersInput{ + Filter: &types.EntityRecognizerFilter{ + RecognizerName: aws.String(name), + }, + } + total := 0 + paginator := comprehend.NewListEntityRecognizersPaginator(conn, input) + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + return err + } + total += len(output.EntityRecognizerPropertiesList) + } + + if total != 0 { + return fmt.Errorf("Expected Comprehend Entity Recognizer (%s) to be destroyed, found %d versions", rs.Primary.ID, total) + } + return nil } return nil @@ -496,6 +650,14 @@ func entityRecognizerIdentity(before, after *types.EntityRecognizerProperties) b return aws.ToTime(before.SubmitTime).Equal(aws.ToTime(after.SubmitTime)) } +func uniqueIDPattern() string { + return prefixedUniqueIDPattern(resource.UniqueIdPrefix) +} + +func prefixedUniqueIDPattern(prefix string) string { + return fmt.Sprintf("%s[[:xdigit:]]{%d}", prefix, resource.UniqueIDSuffixLength) +} + func testAccCheckEntityRecognizerPublishedVersions(name string, count int) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -510,9 +672,14 @@ func testAccCheckEntityRecognizerPublishedVersions(name string, count int) resou conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn ctx := context.Background() + name, err := tfcomprehend.EntityRecognizerParseARN(rs.Primary.ID) + if err != nil { + return err + } + input := &comprehend.ListEntityRecognizersInput{ Filter: &types.EntityRecognizerFilter{ - RecognizerName: aws.String(rs.Primary.Attributes["name"]), + RecognizerName: aws.String(name), }, } total := 0 @@ -631,6 +798,155 @@ resource "aws_s3_object" "entities" { `, rName, vName, key, value)) } +func testAccEntityRecognizerConfig_versionNameEmpty(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + version_name = "" + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_versionNameGenerated(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_versioNamePrefix(rName, versionNamePrefix string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + version_name_prefix = %[2]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName, versionNamePrefix)) +} + func testAccEntityRecognizerConfig_kmsKeyIds(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), diff --git a/internal/service/comprehend/validate.go b/internal/service/comprehend/validate.go index a7a89ba8e12..947a8de8b4b 100644 --- a/internal/service/comprehend/validate.go +++ b/internal/service/comprehend/validate.go @@ -5,22 +5,35 @@ import ( "regexp" "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) const ( - modelIdentifierMaxLen = 63 // Documentation says 256, Console says 63 + modelIdentifierMaxLen = 63 // Documentation says 256, Console says 63 + modelIdentifierPrefixMaxLen = modelIdentifierMaxLen - resource.UniqueIDSuffixLength ) var validModelName = validIdentifier -var validModelVersionName = validIdentifier +var validModelVersionName = validation.Any( // nosemgrep:ci.avoid-string-is-empty-validation + validation.StringIsEmpty, + validIdentifier, +) +var validModelVersionNamePrefix = validIdentifierPrefix var validIdentifier = validation.All( validation.StringLenBetween(1, modelIdentifierMaxLen), - validation.StringMatch(regexp.MustCompile(`[[:alnum:]-]`), "must contain A-Z, a-z, 0-9, and hypen (-)"), + validIdentifierPattern, +) + +var validIdentifierPrefix = validation.All( + validation.StringLenBetween(1, modelIdentifierPrefixMaxLen), + validIdentifierPattern, ) +var validIdentifierPattern = validation.StringMatch(regexp.MustCompile(`^[[:alnum:]-]+$`), "must contain A-Z, a-z, 0-9, and hypen (-)") + var validateKMSKey = validation.Any( validateKMSKeyId, validateKMSKeyARN, diff --git a/website/docs/r/comprehend_entity_recognizer.html.markdown b/website/docs/r/comprehend_entity_recognizer.html.markdown index 58347563f59..7f183859db5 100644 --- a/website/docs/r/comprehend_entity_recognizer.html.markdown +++ b/website/docs/r/comprehend_entity_recognizer.html.markdown @@ -71,8 +71,15 @@ The following arguments are optional: * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` Configuration Block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `version_name` - (Optional) Name for the version of the Entity Recognizer. Each version must have a unique name within the Entity Recognizer. + If omitted, Terraform will assign a random, unique version name. + If explicitly set to `""`, no version name will be set. Has a maximum length of 63 characters. Can contain upper- and lower-case letters, numbers, and hypen (`-`). + Conflicts with `version_name_prefix`. +* `version_name_prefix` - (Optional) Creates a unique version name beginning with the specified prefix. + Has a maximum length of 37 characters. + Can contain upper- and lower-case letters, numbers, and hypen (`-`). + Conflicts with `version_name`. * `volume_kms_key_id` - (Optional) ID or ARN of a KMS Key used to encrypt storage volumes during job processing. * `vpc_config` - (Optional) Configuration parameters for VPC to contain Entity Recognizer resources. See the [`vpc_config` Configuration Block](#vpc_config-configuration-block) section below. From c82ec9f8365969e2bbc3495f76a34e20d3cbb7c5 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 5 Aug 2022 23:43:28 -0700 Subject: [PATCH 15/29] Adds VPC and ENI handling --- internal/diag/append.go | 18 + internal/service/comprehend/acc_test.go | 31 ++ internal/service/comprehend/common_model.go | 95 ++++ internal/service/comprehend/consts.go | 4 +- .../service/comprehend/entity_recognizer.go | 193 ++++++- .../comprehend/entity_recognizer_test.go | 493 +++++++++++++++++- internal/service/comprehend/flex.go | 10 + internal/service/ec2/find.go | 24 +- internal/service/ec2/status.go | 5 +- internal/service/ec2/vpc_network_interface.go | 130 ++++- .../ec2/vpc_network_interface_attachment.go | 3 +- .../ec2/vpc_network_interface_data_source.go | 3 +- .../ec2/vpc_network_interfaces_data_source.go | 3 +- internal/service/ec2/vpc_security_group.go | 55 +- internal/service/ec2/vpc_subnet.go | 7 +- internal/service/ec2/wait.go | 13 +- 16 files changed, 975 insertions(+), 112 deletions(-) create mode 100644 internal/diag/append.go create mode 100644 internal/service/comprehend/common_model.go diff --git a/internal/diag/append.go b/internal/diag/append.go new file mode 100644 index 00000000000..1e336acae7d --- /dev/null +++ b/internal/diag/append.go @@ -0,0 +1,18 @@ +package diag + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +) + +func AppendWarningf(diags diag.Diagnostics, format string, a ...any) diag.Diagnostics { + return append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: fmt.Sprintf(format, a...), + }) +} + +func AppendErrorf(diags diag.Diagnostics, format string, a ...any) diag.Diagnostics { + return append(diags, diag.Errorf(format, a...)...) +} diff --git a/internal/service/comprehend/acc_test.go b/internal/service/comprehend/acc_test.go index 10cd09a6652..f84aa7b63d5 100644 --- a/internal/service/comprehend/acc_test.go +++ b/internal/service/comprehend/acc_test.go @@ -2,6 +2,7 @@ package comprehend_test import ( "context" + "fmt" "testing" "github.com/aws/aws-sdk-go-v2/service/comprehend" @@ -25,3 +26,33 @@ func testAccPreCheck(t *testing.T) { t.Fatalf("unexpected PreCheck error: %s", err) } } + +func configVPCWithSubnetsAndDNS(rName string, subnetCount int) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptInDefaultExclude(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + enable_dns_support = true + enable_dns_hostnames = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + count = %[2]d + + vpc_id = aws_vpc.test.id + availability_zone = element(data.aws_availability_zones.available.names,count.index) + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) + + tags = { + Name = %[1]q + } +} +`, rName, subnetCount), + ) +} diff --git a/internal/service/comprehend/common_model.go b/internal/service/comprehend/common_model.go new file mode 100644 index 00000000000..7fa0c1022f9 --- /dev/null +++ b/internal/service/comprehend/common_model.go @@ -0,0 +1,95 @@ +package comprehend + +import ( + "context" + "strings" + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" +) + +type safeMutex struct { + locked bool + mutex sync.Mutex +} + +func (m *safeMutex) Lock() { + m.mutex.Lock() + m.locked = true +} + +func (m *safeMutex) Unlock() { + if m.locked { + m.locked = false + m.mutex.Unlock() + } +} + +var modelVPCENILock safeMutex + +func findNetworkInterfaces(ctx context.Context, conn *ec2.EC2, securityGroups []string, subnets []string) ([]*ec2.NetworkInterface, error) { + networkInterfaces, err := tfec2.FindNetworkInterfacesWithContext(ctx, conn, &ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + tfec2.NewFilter("group-id", securityGroups), + tfec2.NewFilter("subnet-id", subnets), + }, + }) + if err != nil { + return []*ec2.NetworkInterface{}, err + } + + comprehendENIs := make([]*ec2.NetworkInterface, 0, len(networkInterfaces)) + for _, v := range networkInterfaces { + if strings.HasSuffix(aws.ToString(v.RequesterId), ":Comprehend") { + comprehendENIs = append(comprehendENIs, v) + } + } + + return comprehendENIs, nil +} + +func waitNetworkInterfaceCreated(ctx context.Context, conn *ec2.EC2, initialENIIds map[string]bool, securityGroups []string, subnets []string, timeout time.Duration) (*ec2.NetworkInterface, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{}, + Target: []string{ec2.NetworkInterfaceStatusInUse}, + Refresh: statusNetworkInterfaces(ctx, conn, initialENIIds, securityGroups, subnets), + Delay: 4 * time.Minute, + MinTimeout: 10 * time.Second, + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*ec2.NetworkInterface); ok { + return output, err + } + + return nil, err +} + +func statusNetworkInterfaces(ctx context.Context, conn *ec2.EC2, initialENIs map[string]bool, securityGroups []string, subnets []string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findNetworkInterfaces(ctx, conn, securityGroups, subnets) + if err != nil { + return nil, "", err + } + + var added *ec2.NetworkInterface + for _, v := range out { + if _, ok := initialENIs[aws.ToString(v.NetworkInterfaceId)]; !ok { + added = v + break + } + } + + if added == nil { + return nil, "", nil + } + + return added, aws.ToString(added.Status), nil + } +} diff --git a/internal/service/comprehend/consts.go b/internal/service/comprehend/consts.go index a087df5b0fd..8c926987a46 100644 --- a/internal/service/comprehend/consts.go +++ b/internal/service/comprehend/consts.go @@ -7,5 +7,5 @@ import ( const iamPropagationTimeout = 2 * time.Minute // Avoid service throttling -const entityRegcognizerDelay = 10 * time.Minute -const entityRegcognizerPollInterval = 60 * time.Second +const entityRegcognizerDelay = 1 * time.Minute +const entityRegcognizerPollInterval = 1 * time.Minute diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 3dbc790e16d..aa7fd85a6f1 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/comprehend" "github.com/aws/aws-sdk-go-v2/service/comprehend/types" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -19,7 +20,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" + awsdiag "github.com/hashicorp/terraform-provider-aws/internal/diag" "github.com/hashicorp/terraform-provider-aws/internal/enum" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -75,7 +78,7 @@ func ResourceEntityRecognizer() *schema.Resource { }, }, "augmented_manifests": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -218,12 +221,12 @@ func ResourceEntityRecognizer() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "security_group_ids": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "subnets": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, }, @@ -273,6 +276,11 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay time.Sleep(iamPropagationTimeout) + if in.VpcConfig != nil { + modelVPCENILock.Lock() + defer modelVPCENILock.Unlock() + } + var out *comprehend.CreateEntityRecognizerOutput err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { var err error @@ -302,11 +310,66 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, d.SetId(aws.ToString(out.EntityRecognizerArn)) - if _, err := waitEntityRecognizerCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return diag.Errorf("waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + var g multierror.Group + waitCtx, cancel := context.WithCancel(ctx) + + g.Go(func() error { + _, err := waitEntityRecognizerCreated(waitCtx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)) + cancel() + return err + }) + + var diags diag.Diagnostics + + if in.VpcConfig != nil { + g.Go(func() error { + ec2Conn := meta.(*conns.AWSClient).EC2Conn + enis, err := findNetworkInterfaces(waitCtx, ec2Conn, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets) + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + return nil + } + initialENIIds := make(map[string]bool, len(enis)) + for _, v := range enis { + initialENIIds[aws.ToString(v.NetworkInterfaceId)] = true + } + + newENI, err := waitNetworkInterfaceCreated(waitCtx, ec2Conn, initialENIIds, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets, d.Timeout(schema.TimeoutCreate)) + if errors.Is(err, context.Canceled) { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), "ENI not found") + return nil + } + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + return nil + } + + modelVPCENILock.Unlock() + + _, err = ec2Conn.CreateTagsWithContext(waitCtx, &ec2.CreateTagsInput{ + Resources: []*string{newENI.NetworkInterfaceId}, + Tags: []*ec2.Tag{ + { + Key: aws.String("tf-aws_comprehend_entity_recognizer"), + Value: aws.String(d.Id()), + }, + }, + }) + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + return nil + } + + return nil + }) } - return resourceEntityRecognizerRead(ctx, d, meta) + err = g.Wait().ErrorOrNil() + if err != nil { + return awsdiag.AppendErrorf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + } + + return append(diags, resourceEntityRecognizerRead(ctx, d, meta)...) } func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -370,6 +433,8 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).ComprehendConn + var diags diag.Diagnostics + if d.HasChangesExcept("tags", "tags_all") { in := &comprehend.CreateEntityRecognizerInput{ DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), @@ -405,6 +470,11 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay time.Sleep(iamPropagationTimeout) + if in.VpcConfig != nil { + modelVPCENILock.Lock() + defer modelVPCENILock.Unlock() + } + var out *comprehend.CreateEntityRecognizerOutput err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { var err error @@ -434,8 +504,61 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, d.SetId(aws.ToString(out.EntityRecognizerArn)) - if _, err := waitEntityRecognizerCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return diag.Errorf("waiting for Amazon Comprehend Entity Recognizer (%s) to be updated: %s", d.Id(), err) + var g multierror.Group + waitCtx, cancel := context.WithCancel(ctx) + + g.Go(func() error { + _, err := waitEntityRecognizerCreated(waitCtx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)) + cancel() + return err + }) + + if in.VpcConfig != nil { + g.Go(func() error { + ec2Conn := meta.(*conns.AWSClient).EC2Conn + enis, err := findNetworkInterfaces(waitCtx, ec2Conn, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets) + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + return nil + } + initialENIIds := make(map[string]bool, len(enis)) + for _, v := range enis { + initialENIIds[aws.ToString(v.NetworkInterfaceId)] = true + } + + newENI, err := waitNetworkInterfaceCreated(waitCtx, ec2Conn, initialENIIds, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets, d.Timeout(schema.TimeoutCreate)) + if errors.Is(err, context.Canceled) { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), "ENI not found") + return nil + } + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + return nil + } + + modelVPCENILock.Unlock() + + _, err = ec2Conn.CreateTagsWithContext(waitCtx, &ec2.CreateTagsInput{ + Resources: []*string{newENI.NetworkInterfaceId}, + Tags: []*ec2.Tag{ + { + Key: aws.String("tf-aws_comprehend_entity_recognizer"), + Value: aws.String(d.Id()), + }, + }, + }) + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + return nil + } + + return nil + }) + } + + err = g.Wait().ErrorOrNil() + if err != nil { + return awsdiag.AppendErrorf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be updated: %s", d.Id(), err) } } else if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") @@ -445,7 +568,7 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, } } - return resourceEntityRecognizerRead(ctx, d, meta) + return append(diags, resourceEntityRecognizerRead(ctx, d, meta)...) } func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -504,6 +627,38 @@ func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, return fmt.Errorf("waiting for version (%s) to be deleted: %s", aws.ToString(v.VersionName), err) } + ec2Conn := meta.(*conns.AWSClient).EC2Conn + networkInterfaces, err := tfec2.FindNetworkInterfacesWithContext(ctx, ec2Conn, &ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + tfec2.NewFilter(fmt.Sprintf("tag:%s", "tf-aws_comprehend_entity_recognizer"), []string{aws.ToString(v.EntityRecognizerArn)}), + }, + }) + if err != nil { + return fmt.Errorf("finding ENIs for version (%s): %w", aws.ToString(v.VersionName), err) + } + + for _, v := range networkInterfaces { + v := v + g.Go(func() error { + networkInterfaceID := aws.ToString(v.NetworkInterfaceId) + + if v.Attachment != nil { + err = tfec2.DetachNetworkInterfaceWithContext(ctx, ec2Conn, networkInterfaceID, aws.ToString(v.Attachment.AttachmentId), d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return fmt.Errorf("detaching ENI (%s): %w", networkInterfaceID, err) + } + } + + err = tfec2.DeleteNetworkInterfaceWithContext(ctx, ec2Conn, networkInterfaceID) + if err != nil { + return fmt.Errorf("deleting ENI (%s): %w", networkInterfaceID, err) + } + + return nil + }) + } + return nil }) } @@ -586,8 +741,8 @@ func waitEntityRecognizerCreated(ctx context.Context, conn *comprehend.Client, i func waitEntityRecognizerStopped(ctx context.Context, conn *comprehend.Client, id string, timeout time.Duration) (*types.EntityRecognizerProperties, error) { stateConf := &resource.StateChangeConf{ - Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusDeleting, types.ModelStatusStopRequested), - Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError), + Pending: enum.Slice(types.ModelStatusSubmitted, types.ModelStatusTraining, types.ModelStatusStopRequested), + Target: enum.Slice(types.ModelStatusTrained, types.ModelStatusStopped, types.ModelStatusInError, types.ModelStatusDeleting), Refresh: statusEntityRecognizer(ctx, conn, id), PollInterval: entityRegcognizerPollInterval, Timeout: timeout, @@ -766,8 +921,8 @@ func flattenVPCConfig(apiObject *types.VpcConfig) []interface{} { } m := map[string]interface{}{ - "security_group_ids": apiObject.SecurityGroupIds, - "subnets": apiObject.Subnets, + "security_group_ids": FlattenStringSet(apiObject.SecurityGroupIds), + "subnets": FlattenStringSet(apiObject.Subnets), } return []interface{}{m} @@ -783,7 +938,7 @@ func expandInputDataConfig(tfList []interface{}) *types.EntityRecognizerInputDat a := &types.EntityRecognizerInputDataConfig{ EntityTypes: expandEntityTypes(tfMap["entity_types"].(*schema.Set)), Annotations: expandAnnotations(tfMap["annotations"].([]interface{})), - AugmentedManifests: expandAugmentedManifests(tfMap["augmented_manifests"].([]interface{})), + AugmentedManifests: expandAugmentedManifests(tfMap["augmented_manifests"].(*schema.Set)), DataFormat: types.EntityRecognizerDataFormat(tfMap["data_format"].(string)), Documents: expandDocuments(tfMap["documents"].([]interface{})), EntityList: expandEntityList(tfMap["entity_list"].([]interface{})), @@ -848,14 +1003,14 @@ func expandAnnotations(tfList []interface{}) *types.EntityRecognizerAnnotations return a } -func expandAugmentedManifests(tfList []interface{}) []types.AugmentedManifestsListItem { - if len(tfList) == 0 { +func expandAugmentedManifests(tfSet *schema.Set) []types.AugmentedManifestsListItem { + if tfSet.Len() == 0 { return nil } var s []types.AugmentedManifestsListItem - for _, r := range tfList { + for _, r := range tfSet.List() { m, ok := r.(map[string]interface{}) if !ok { @@ -938,8 +1093,8 @@ func expandVPCConfig(tfList []interface{}) *types.VpcConfig { tfMap := tfList[0].(map[string]interface{}) a := &types.VpcConfig{ - SecurityGroupIds: ExpandStringList(tfMap["security_group_ids"].([]interface{})), - Subnets: ExpandStringList(tfMap["subnets"].([]interface{})), + SecurityGroupIds: ExpandStringSet(tfMap["security_group_ids"].(*schema.Set)), + Subnets: ExpandStringSet(tfMap["subnets"].(*schema.Set)), } return a diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 1964e33ee82..389c4652db8 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -431,6 +431,127 @@ func TestAccComprehendEntityRecognizer_KMSKeys_Update(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_VPCConfig_Create(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var er1, er2 types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_vpcConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &er1), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.security_group_ids.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.security_group_ids.*", "aws_security_group.test.0", "id"), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.subnets.#", "2"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.subnets.*", "aws_subnet.test.0", "id"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.subnets.*", "aws_subnet.test.1", "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccEntityRecognizerConfig_vpcConfig_Update(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &er2), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 2), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.security_group_ids.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.security_group_ids.*", "aws_security_group.test.1", "id"), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.subnets.#", "2"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.subnets.*", "aws_subnet.test.2", "id"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.subnets.*", "aws_subnet.test.3", "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_VPCConfig_Update(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var er1, er2, er3 types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_vpcConfig_None(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &er1), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), + ), + }, + { + Config: testAccEntityRecognizerConfig_vpcConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &er2), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 2), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.security_group_ids.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.security_group_ids.*", "aws_security_group.test.0", "id"), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.subnets.#", "2"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.subnets.*", "aws_subnet.test.0", "id"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "vpc_config.0.subnets.*", "aws_subnet.test.1", "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccEntityRecognizerConfig_vpcConfig_None(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &er3), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 3), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComprehendEntityRecognizer_tags(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -726,7 +847,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -780,7 +901,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -830,7 +951,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -879,7 +1000,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -929,7 +1050,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -981,7 +1102,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1068,7 +1189,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1152,7 +1273,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1204,7 +1325,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1291,7 +1412,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1377,7 +1498,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1430,7 +1551,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1484,7 +1605,7 @@ resource "aws_comprehend_entity_recognizer" "test" { } depends_on = [ - aws_iam_role_policy.test + aws_iam_role_policy.test, ] } @@ -1576,3 +1697,349 @@ data "aws_iam_policy_document" "role" { } `, rName) } + +func testAccEntityRecognizerConfig_vpcRole() string { + return ` +resource "aws_iam_role_policy" "vpc_access" { + role = aws_iam_role.test.name + + policy = data.aws_iam_policy_document.vpc_access.json +} + +data "aws_iam_policy_document" "vpc_access" { + statement { + actions = [ + "ec2:CreateNetworkInterface", + "ec2:CreateNetworkInterfacePermission", + "ec2:DeleteNetworkInterface", + "ec2:DeleteNetworkInterfacePermission", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeVpcs", + "ec2:DescribeDhcpOptions", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + ] + + resources = [ + "*", + ] + } +} +` +} + +func testAccEntityRecognizerConfig_vpcConfig(rName string) string { + const subnetCount = 2 + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerConfig_vpcRole(), + testAccEntityRecognizerS3BucketConfig(rName), + configVPCWithSubnetsAndDNS(rName, subnetCount), + fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + vpc_config { + security_group_ids = [aws_security_group.test[0].id] + subnets = aws_subnet.test[*].id + } + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + aws_iam_role_policy.vpc_access, + aws_vpc_endpoint_route_table_association.test, + ] +} + +resource "aws_security_group" "test" { + count = 1 + + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + self = true + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id] + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id +} + +resource "aws_route_table_association" "test" { + count = length(aws_subnet.test) + + subnet_id = aws_subnet.test[count.index].id + route_table_id = aws_route_table.test.id +} + +resource "aws_vpc_endpoint_route_table_association" "test" { + route_table_id = aws_route_table.test.id + vpc_endpoint_id = aws_vpc_endpoint.s3.id +} + +resource "aws_vpc_endpoint" "s3" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" +} + +resource "aws_vpc_endpoint_policy" "s3" { + vpc_endpoint_id = aws_vpc_endpoint.s3.id + + policy = data.aws_iam_policy_document.s3_endpoint.json +} + +data "aws_iam_policy_document" "s3_endpoint" { + statement { + principals { + type = "AWS" + identifiers = ["*"] + } + + actions = [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:DeleteObject", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload", + ] + + resources = [ + "*", + ] + } +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_vpcConfig_Update(rName string) string { + const subnetCount = 4 + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerConfig_vpcRole(), + testAccEntityRecognizerS3BucketConfig(rName), + configVPCWithSubnetsAndDNS(rName, subnetCount), + fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + vpc_config { + security_group_ids = [aws_security_group.test[1].id] + subnets = slice(aws_subnet.test[*].id, 2, 4) + } + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + aws_iam_role_policy.vpc_access, + aws_vpc_endpoint_route_table_association.test, + ] +} + +resource "aws_security_group" "test" { + count = 2 + + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + self = true + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id] + } + } + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id +} + +resource "aws_route_table_association" "test" { + count = length(aws_subnet.test) + + subnet_id = aws_subnet.test[count.index].id + route_table_id = aws_route_table.test.id +} + +resource "aws_vpc_endpoint_route_table_association" "test" { + route_table_id = aws_route_table.test.id + vpc_endpoint_id = aws_vpc_endpoint.s3.id +} + +resource "aws_vpc_endpoint" "s3" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" +} + +resource "aws_vpc_endpoint_policy" "s3" { + vpc_endpoint_id = aws_vpc_endpoint.s3.id + + policy = data.aws_iam_policy_document.s3_endpoint.json +} + +data "aws_iam_policy_document" "s3_endpoint" { + statement { + principals { + type = "AWS" + identifiers = ["*"] + } + + actions = [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:DeleteObject", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload", + ] + + resources = [ + "*", + ] + } +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} + +func testAccEntityRecognizerConfig_vpcConfig_None(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerConfig_vpcRole(), + testAccEntityRecognizerS3BucketConfig(rName), + fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + ] +} + +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "entities" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/entitylist.csv" +} +`, rName)) +} diff --git a/internal/service/comprehend/flex.go b/internal/service/comprehend/flex.go index fb0a9d6eabe..89c79e39026 100644 --- a/internal/service/comprehend/flex.go +++ b/internal/service/comprehend/flex.go @@ -1,5 +1,7 @@ package comprehend +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + // Takes the result of flatmap.Expand for an array of strings // and returns a []string func ExpandStringList(configured []interface{}) []string { @@ -13,6 +15,10 @@ func ExpandStringList(configured []interface{}) []string { return vs } +func ExpandStringSet(configured *schema.Set) []string { + return ExpandStringList(configured.List()) +} + // Takes list of strings. Expand to an array // of raw strings and returns a []interface{} // to keep compatibility w/ schema.NewSet @@ -23,3 +29,7 @@ func FlattenStringList(list []string) []interface{} { } return vs } + +func FlattenStringSet(list []string) *schema.Set { + return schema.NewSet(schema.HashString, FlattenStringList(list)) +} diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index aa510f820ab..61220b9cb2b 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1333,8 +1333,8 @@ func FindNetworkACLEntryByThreePartKey(conn *ec2.EC2, naclID string, egress bool return nil, &resource.NotFoundError{} } -func FindNetworkInterface(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) (*ec2.NetworkInterface, error) { - output, err := FindNetworkInterfaces(conn, input) +func FindNetworkInterfaceWithContext(ctx context.Context, conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) (*ec2.NetworkInterface, error) { + output, err := FindNetworkInterfacesWithContext(ctx, conn, input) if err != nil { return nil, err @@ -1351,10 +1351,10 @@ func FindNetworkInterface(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInp return output[0], nil } -func FindNetworkInterfaces(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) ([]*ec2.NetworkInterface, error) { +func FindNetworkInterfacesWithContext(ctx context.Context, conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) ([]*ec2.NetworkInterface, error) { var output []*ec2.NetworkInterface - err := conn.DescribeNetworkInterfacesPages(input, func(page *ec2.DescribeNetworkInterfacesOutput, lastPage bool) bool { + err := conn.DescribeNetworkInterfacesPagesWithContext(ctx, input, func(page *ec2.DescribeNetworkInterfacesOutput, lastPage bool) bool { if page == nil { return !lastPage } @@ -1383,11 +1383,15 @@ func FindNetworkInterfaces(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesIn } func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, error) { + return FindNetworkInterfaceByIDWithContext(context.Background(), conn, id) +} + +func FindNetworkInterfaceByIDWithContext(ctx context.Context, conn *ec2.EC2, id string) (*ec2.NetworkInterface, error) { input := &ec2.DescribeNetworkInterfacesInput{ NetworkInterfaceIds: aws.StringSlice([]string{id}), } - output, err := FindNetworkInterface(conn, input) + output, err := FindNetworkInterfaceWithContext(ctx, conn, input) if err != nil { return nil, err @@ -1404,6 +1408,10 @@ func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, } func FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(conn *ec2.EC2, attachmentInstanceOwnerID, description string) ([]*ec2.NetworkInterface, error) { + return FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescriptionWithContext(context.Background(), conn, attachmentInstanceOwnerID, description) +} + +func FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescriptionWithContext(ctx context.Context, conn *ec2.EC2, attachmentInstanceOwnerID, description string) ([]*ec2.NetworkInterface, error) { input := &ec2.DescribeNetworkInterfacesInput{ Filters: BuildAttributeFilterList(map[string]string{ "attachment.instance-owner-id": attachmentInstanceOwnerID, @@ -1411,17 +1419,17 @@ func FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(conn *ec2.EC }), } - return FindNetworkInterfaces(conn, input) + return FindNetworkInterfacesWithContext(ctx, conn, input) } -func FindNetworkInterfaceAttachmentByID(conn *ec2.EC2, id string) (*ec2.NetworkInterfaceAttachment, error) { +func FindNetworkInterfaceAttachmentByID(ctx context.Context, conn *ec2.EC2, id string) (*ec2.NetworkInterfaceAttachment, error) { input := &ec2.DescribeNetworkInterfacesInput{ Filters: BuildAttributeFilterList(map[string]string{ "attachment.attachment-id": id, }), } - networkInterface, err := FindNetworkInterface(conn, input) + networkInterface, err := FindNetworkInterfaceWithContext(ctx, conn, input) if err != nil { return nil, err diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index 43b87c56d1a..a21edad4d50 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "fmt" "strconv" @@ -1125,9 +1126,9 @@ func StatusNetworkInterfaceStatus(conn *ec2.EC2, id string) resource.StateRefres } } -func StatusNetworkInterfaceAttachmentStatus(conn *ec2.EC2, id string) resource.StateRefreshFunc { +func StatusNetworkInterfaceAttachmentStatus(ctx context.Context, conn *ec2.EC2, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindNetworkInterfaceAttachmentByID(conn, id) + output, err := FindNetworkInterfaceAttachmentByID(ctx, conn, id) if tfresource.NotFound(err) { return nil, "", nil diff --git a/internal/service/ec2/vpc_network_interface.go b/internal/service/ec2/vpc_network_interface.go index 090cc23930f..4c648a643a9 100644 --- a/internal/service/ec2/vpc_network_interface.go +++ b/internal/service/ec2/vpc_network_interface.go @@ -4,12 +4,14 @@ import ( "context" "fmt" "log" + "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -1106,7 +1108,7 @@ func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string attachmentID := aws.StringValue(output.AttachmentId) - _, err = WaitNetworkInterfaceAttached(conn, attachmentID, timeout) + _, err = WaitNetworkInterfaceAttached(context.TODO(), conn, attachmentID, timeout) if err != nil { return attachmentID, fmt.Errorf("error waiting for EC2 Network Interface (%s/%s) attach: %w", networkInterfaceID, attachmentID, err) @@ -1116,8 +1118,12 @@ func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string } func DeleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { + return DeleteNetworkInterfaceWithContext(context.Background(), conn, networkInterfaceID) +} + +func DeleteNetworkInterfaceWithContext(ctx context.Context, conn *ec2.EC2, networkInterfaceID string) error { log.Printf("[INFO] Deleting EC2 Network Interface: %s", networkInterfaceID) - _, err := conn.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{ + _, err := conn.DeleteNetworkInterfaceWithContext(ctx, &ec2.DeleteNetworkInterfaceInput{ NetworkInterfaceId: aws.String(networkInterfaceID), }) @@ -1133,13 +1139,17 @@ func DeleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { } func DetachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID string, timeout time.Duration) error { + return DetachNetworkInterfaceWithContext(context.Background(), conn, networkInterfaceID, attachmentID, timeout) +} + +func DetachNetworkInterfaceWithContext(ctx context.Context, conn *ec2.EC2, networkInterfaceID, attachmentID string, timeout time.Duration) error { input := &ec2.DetachNetworkInterfaceInput{ AttachmentId: aws.String(attachmentID), Force: aws.Bool(true), } log.Printf("[INFO] Detaching EC2 Network Interface: %s", input) - _, err := conn.DetachNetworkInterface(input) + _, err := conn.DetachNetworkInterfaceWithContext(ctx, input) if tfawserr.ErrCodeEquals(err, errCodeInvalidAttachmentIDNotFound) { return nil @@ -1149,7 +1159,7 @@ func DetachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID stri return fmt.Errorf("detaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, attachmentID, err) } - _, err = WaitNetworkInterfaceDetached(conn, attachmentID, timeout) + _, err = WaitNetworkInterfaceDetached(ctx, conn, attachmentID, timeout) if tfresource.NotFound(err) { return nil @@ -1505,3 +1515,115 @@ func flattenIPv6PrefixSpecifications(apiObjects []*ec2.Ipv6PrefixSpecification) return tfList } + +// Some AWS services creates ENIs behind the scenes and keeps these around for a while +// which can prevent security groups and subnets attached to such ENIs from being destroyed +func deleteLingeringENIs(ctx context.Context, conn *ec2.EC2, filterName, resourceId string, timeout time.Duration) error { + var g multierror.Group + + err := multierror.Append(nil, deleteLingeringLambdaENIs(ctx, &g, conn, filterName, resourceId, timeout)) + + err = multierror.Append(err, deleteLingeringComprehendENIs(ctx, &g, conn, filterName, resourceId, timeout)) + + return multierror.Append(err, g.Wait()).ErrorOrNil() +} + +func deleteLingeringLambdaENIs(ctx context.Context, g *multierror.Group, conn *ec2.EC2, filterName, resourceId string, timeout time.Duration) error { + // AWS Lambda service team confirms P99 deletion time of ~35 minutes. Buffer for safety. + if minimumTimeout := 45 * time.Minute; timeout < minimumTimeout { + timeout = minimumTimeout + } + + networkInterfaces, err := FindNetworkInterfacesWithContext(ctx, conn, &ec2.DescribeNetworkInterfacesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + filterName: resourceId, + "description": "AWS Lambda VPC ENI*", + }), + }) + + if err != nil { + return fmt.Errorf("error listing EC2 Network Interfaces: %w", err) + } + + for _, v := range networkInterfaces { + networkInterfaceID := aws.StringValue(v.NetworkInterfaceId) + + if v.Attachment != nil && aws.StringValue(v.Attachment.InstanceOwnerId) == "amazon-aws" { + networkInterface, err := WaitNetworkInterfaceAvailableAfterUse(conn, networkInterfaceID, timeout) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return fmt.Errorf("waiting for Lambda ENI (%s) to become available for detachment: %w", networkInterfaceID, err) + } + + v = networkInterface + } + + if v.Attachment != nil { + err = DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout) + + if err != nil { + return fmt.Errorf("detaching Lambda ENI (%s): %w", networkInterfaceID, err) + } + } + + err = DeleteNetworkInterface(conn, networkInterfaceID) + + if err != nil { + return fmt.Errorf("deleting Lambda ENI (%s): %w", networkInterfaceID, err) + } + } + + return nil +} + +func deleteLingeringComprehendENIs(ctx context.Context, g *multierror.Group, conn *ec2.EC2, filterName, resourceId string, timeout time.Duration) error { + // Deletion appears to take approximately 5 minutes + if minimumTimeout := 10 * time.Minute; timeout < minimumTimeout { + timeout = minimumTimeout + } + + enis, err := FindNetworkInterfacesWithContext(ctx, conn, &ec2.DescribeNetworkInterfacesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + filterName: resourceId, + }), + }) + if err != nil { + return fmt.Errorf("error listing EC2 Network Interfaces: %w", err) + } + + networkInterfaces := make([]*ec2.NetworkInterface, 0, len(enis)) + for _, v := range enis { + if strings.HasSuffix(aws.StringValue(v.RequesterId), ":Comprehend") { + networkInterfaces = append(networkInterfaces, v) + } + } + + for _, v := range networkInterfaces { + v := v + g.Go(func() error { + networkInterfaceID := aws.StringValue(v.NetworkInterfaceId) + + if v.Attachment != nil { + err = DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout) + + if err != nil { + return fmt.Errorf("detaching Comprehend ENI (%s): %w", networkInterfaceID, err) + } + } + + err := DeleteNetworkInterface(conn, networkInterfaceID) + + if err != nil { + return fmt.Errorf("deleting Comprehend ENI (%s): %w", networkInterfaceID, err) + } + + return nil + }) + } + + return nil +} diff --git a/internal/service/ec2/vpc_network_interface_attachment.go b/internal/service/ec2/vpc_network_interface_attachment.go index 05584f1375a..1c7c49bea13 100644 --- a/internal/service/ec2/vpc_network_interface_attachment.go +++ b/internal/service/ec2/vpc_network_interface_attachment.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "fmt" "log" @@ -68,7 +69,7 @@ func resourceNetworkInterfaceAttachmentCreate(d *schema.ResourceData, meta inter func resourceNetworkInterfaceAttachmentRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - attachment, err := FindNetworkInterfaceAttachmentByID(conn, d.Id()) + attachment, err := FindNetworkInterfaceAttachmentByID(context.TODO(), conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] EC2 Network Interface Attachment (%s) not found, removing from state", d.Id()) diff --git a/internal/service/ec2/vpc_network_interface_data_source.go b/internal/service/ec2/vpc_network_interface_data_source.go index 67065d8fcb7..60e806067b1 100644 --- a/internal/service/ec2/vpc_network_interface_data_source.go +++ b/internal/service/ec2/vpc_network_interface_data_source.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "fmt" "time" @@ -169,7 +170,7 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er input.NetworkInterfaceIds = []*string{aws.String(v.(string))} } - eni, err := FindNetworkInterface(conn, input) + eni, err := FindNetworkInterfaceWithContext(context.TODO(), conn, input) if err != nil { return fmt.Errorf("error reading EC2 Network Interface: %w", err) diff --git a/internal/service/ec2/vpc_network_interfaces_data_source.go b/internal/service/ec2/vpc_network_interfaces_data_source.go index 5b25cc006e4..57e0496afe7 100644 --- a/internal/service/ec2/vpc_network_interfaces_data_source.go +++ b/internal/service/ec2/vpc_network_interfaces_data_source.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "fmt" "time" @@ -50,7 +51,7 @@ func dataSourceNetworkInterfacesRead(d *schema.ResourceData, meta interface{}) e networkInterfaceIDs := []string{} - output, err := FindNetworkInterfaces(conn, input) + output, err := FindNetworkInterfacesWithContext(context.TODO(), conn, input) if err != nil { return fmt.Errorf("error reading EC2 Network Interfaces: %w", err) diff --git a/internal/service/ec2/vpc_security_group.go b/internal/service/ec2/vpc_security_group.go index 649c57e053c..17d634a44be 100644 --- a/internal/service/ec2/vpc_security_group.go +++ b/internal/service/ec2/vpc_security_group.go @@ -2,6 +2,7 @@ package ec2 import ( "bytes" + "context" "fmt" "log" "regexp" @@ -363,8 +364,8 @@ func resourceSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error func resourceSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - if err := deleteLingeringLambdaENIs(conn, "group-id", d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("deleting Lambda ENIs using Security Group (%s): %w", d.Id(), err) + if err := deleteLingeringENIs(context.TODO(), conn, "group-id", d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("deleting ENIs using Security Group (%s): %w", d.Id(), err) } // conditionally revoke rules first before attempting to delete the group @@ -1263,56 +1264,6 @@ var securityGroupProtocolIntegers = map[string]int{ "all": -1, } -// The AWS Lambda service creates ENIs behind the scenes and keeps these around for a while -// which would prevent SGs attached to such ENIs from being destroyed -func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceID string, timeout time.Duration) error { - // AWS Lambda service team confirms P99 deletion time of ~35 minutes. Buffer for safety. - if minimumTimeout := 45 * time.Minute; timeout < minimumTimeout { - timeout = minimumTimeout - } - - networkInterfaces, err := FindNetworkInterfaces(conn, &ec2.DescribeNetworkInterfacesInput{ - Filters: BuildAttributeFilterList(map[string]string{ - filterName: resourceID, - "description": "AWS Lambda VPC ENI*", - }), - }) - - if err != nil { - return fmt.Errorf("listing EC2 Network Interfaces: %w", err) - } - - for _, v := range networkInterfaces { - networkInterfaceID := aws.StringValue(v.NetworkInterfaceId) - - if v.Attachment != nil && aws.StringValue(v.Attachment.InstanceOwnerId) == "amazon-aws" { - networkInterface, err := WaitNetworkInterfaceAvailableAfterUse(conn, networkInterfaceID, timeout) - - if tfresource.NotFound(err) { - continue - } - - if err != nil { - return fmt.Errorf("waiting for Lambda ENI (%s) to become available after use: %w", networkInterfaceID, err) - } - - v = networkInterface - } - - if v.Attachment != nil { - if err := DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout); err != nil { - return err - } - } - - if err := DeleteNetworkInterface(conn, networkInterfaceID); err != nil { - return err - } - } - - return nil -} - func initSecurityGroupRule(ruleMap map[string]map[string]interface{}, perm *ec2.IpPermission, desc string) map[string]interface{} { var fromPort, toPort int64 if v := perm.FromPort; v != nil { diff --git a/internal/service/ec2/vpc_subnet.go b/internal/service/ec2/vpc_subnet.go index 973648a0ec5..584b43d1f23 100644 --- a/internal/service/ec2/vpc_subnet.go +++ b/internal/service/ec2/vpc_subnet.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "fmt" "log" "time" @@ -361,8 +362,8 @@ func resourceSubnetDelete(d *schema.ResourceData, meta interface{}) error { log.Printf("[INFO] Deleting EC2 Subnet: %s", d.Id()) - if err := deleteLingeringLambdaENIs(conn, "subnet-id", d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error deleting Lambda ENIs using EC2 Subnet (%s): %w", d.Id(), err) + if err := deleteLingeringENIs(context.TODO(), conn, "subnet-id", d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("deleting ENIs for EC2 Subnet (%s): %w", d.Id(), err) } _, err := tfresource.RetryWhenAWSErrCodeEquals(d.Timeout(schema.TimeoutDelete), func() (interface{}, error) { @@ -376,7 +377,7 @@ func resourceSubnetDelete(d *schema.ResourceData, meta interface{}) error { } if err != nil { - return fmt.Errorf("error deleting EC2 Subnet (%s): %w", d.Id(), err) + return fmt.Errorf("deleting EC2 Subnet (%s): %w", d.Id(), err) } return nil diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index ab023150070..198c7734f35 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "errors" "fmt" "strconv" @@ -2274,15 +2275,15 @@ const ( NetworkInterfaceDetachedTimeout = 10 * time.Minute ) -func WaitNetworkInterfaceAttached(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { +func WaitNetworkInterfaceAttached(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AttachmentStatusAttaching}, Target: []string{ec2.AttachmentStatusAttached}, Timeout: timeout, - Refresh: StatusNetworkInterfaceAttachmentStatus(conn, id), + Refresh: StatusNetworkInterfaceAttachmentStatus(ctx, conn, id), } - outputRaw, err := stateConf.WaitForState() + outputRaw, err := stateConf.WaitForStateContext(ctx) if output, ok := outputRaw.(*ec2.NetworkInterfaceAttachment); ok { return output, err @@ -2332,15 +2333,15 @@ func WaitNetworkInterfaceCreated(conn *ec2.EC2, id string, timeout time.Duration return nil, err } -func WaitNetworkInterfaceDetached(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { +func WaitNetworkInterfaceDetached(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AttachmentStatusDetaching}, Target: []string{ec2.AttachmentStatusDetached}, Timeout: timeout, - Refresh: StatusNetworkInterfaceAttachmentStatus(conn, id), + Refresh: StatusNetworkInterfaceAttachmentStatus(ctx, conn, id), } - outputRaw, err := stateConf.WaitForState() + outputRaw, err := stateConf.WaitForStateContext(ctx) if output, ok := outputRaw.(*ec2.NetworkInterfaceAttachment); ok { return output, err From f56e614eb55745dca4057e8decfe1dca57111c8e Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 8 Aug 2022 10:53:14 -0700 Subject: [PATCH 16/29] Updates test to ensure tags are updated when a version is published --- internal/service/comprehend/entity_recognizer.go | 2 ++ internal/service/comprehend/entity_recognizer_test.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index aa7fd85a6f1..8755231c7b0 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -561,6 +561,8 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, return awsdiag.AppendErrorf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be updated: %s", d.Id(), err) } } else if d.HasChange("tags_all") { + // For a tags-only change. If tag changes are combined with version publishing, the tags are set + // by the CreateEntityRecognizer call o, n := d.GetChange("tags_all") if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 389c4652db8..96d45880c11 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -159,6 +159,11 @@ func TestAccComprehendEntityRecognizer_versionName(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.key", "value2"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } From 803d8562aea175b33c83738be32df751e1510ef6 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 8 Aug 2022 11:33:19 -0700 Subject: [PATCH 17/29] Refactors S3 object setup --- internal/service/comprehend/acc_test.go | 2 +- .../comprehend/entity_recognizer_test.go | 204 ++---------------- 2 files changed, 22 insertions(+), 184 deletions(-) diff --git a/internal/service/comprehend/acc_test.go b/internal/service/comprehend/acc_test.go index f84aa7b63d5..f52bdec3e90 100644 --- a/internal/service/comprehend/acc_test.go +++ b/internal/service/comprehend/acc_test.go @@ -46,7 +46,7 @@ resource "aws_subnet" "test" { count = %[2]d vpc_id = aws_vpc.test.id - availability_zone = element(data.aws_availability_zones.available.names,count.index) + availability_zone = element(data.aws_availability_zones.available.names, count.index) cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) tags = { diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 96d45880c11..76558998c5a 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -825,6 +825,7 @@ func testAccEntityRecognizerConfig_basic(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -855,18 +856,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -874,6 +863,7 @@ func testAccEntityRecognizerConfig_versionName(rName, vName, key, value string) return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -909,18 +899,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName, vName, key, value)) } @@ -928,6 +906,7 @@ func testAccEntityRecognizerConfig_versionNameEmpty(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -959,18 +938,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -978,6 +945,7 @@ func testAccEntityRecognizerConfig_versionNameGenerated(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1008,18 +976,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1027,6 +983,7 @@ func testAccEntityRecognizerConfig_versioNamePrefix(rName, versionNamePrefix str return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1058,18 +1015,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName, versionNamePrefix)) } @@ -1077,6 +1022,7 @@ func testAccEntityRecognizerConfig_kmsKeyIds(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1145,18 +1091,6 @@ resource "aws_kms_key" "model" { resource "aws_kms_key" "volume" { deletion_window_in_days = 7 } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1164,6 +1098,7 @@ func testAccEntityRecognizerConfig_kmsKeyARNs(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1232,18 +1167,6 @@ resource "aws_kms_key" "model" { resource "aws_kms_key" "volume" { deletion_window_in_days = 7 } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1251,6 +1174,7 @@ func testAccEntityRecognizerConfig_kmsKeys_None(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1281,18 +1205,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1300,6 +1212,7 @@ func testAccEntityRecognizerConfig_kmsKeys_Set(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1368,18 +1281,6 @@ resource "aws_kms_key" "model" { resource "aws_kms_key" "volume" { deletion_window_in_days = 7 } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1387,6 +1288,7 @@ func testAccEntityRecognizerConfig_kmsKeys_Update(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1455,18 +1357,6 @@ resource "aws_kms_key" "model2" { resource "aws_kms_key" "volume2" { deletion_window_in_days = 7 } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1474,6 +1364,7 @@ func testAccEntityRecognizerConfig_tags0(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1506,18 +1397,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1525,6 +1404,7 @@ func testAccEntityRecognizerConfig_tags1(rName, tagKey1, tagValue1 string) strin return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1559,18 +1439,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName, tagKey1, tagValue1)) } @@ -1578,6 +1446,7 @@ func testAccEntityRecognizerConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tag return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1613,18 +1482,6 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } @@ -1740,6 +1597,7 @@ func testAccEntityRecognizerConfig_vpcConfig(rName string) string { testAccEntityRecognizerConfig_vpcRole(), testAccEntityRecognizerS3BucketConfig(rName), configVPCWithSubnetsAndDNS(rName, subnetCount), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1850,18 +1708,6 @@ data "aws_iam_policy_document" "s3_endpoint" { ] } } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -1872,6 +1718,7 @@ func testAccEntityRecognizerConfig_vpcConfig_Update(rName string) string { testAccEntityRecognizerConfig_vpcRole(), testAccEntityRecognizerS3BucketConfig(rName), configVPCWithSubnetsAndDNS(rName, subnetCount), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1931,7 +1778,7 @@ resource "aws_security_group" "test" { protocol = "-1" prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id] } - } +} resource "aws_route_table" "test" { vpc_id = aws_vpc.test.id @@ -1982,18 +1829,6 @@ data "aws_iam_policy_document" "s3_endpoint" { ] } } - -resource "aws_s3_object" "documents" { - bucket = aws_s3_bucket.test.bucket - key = "documents.txt" - source = "test-fixtures/entity_recognizer/documents.txt" -} - -resource "aws_s3_object" "entities" { - bucket = aws_s3_bucket.test.bucket - key = "entitylist.csv" - source = "test-fixtures/entity_recognizer/entitylist.csv" -} `, rName)) } @@ -2002,6 +1837,7 @@ func testAccEntityRecognizerConfig_vpcConfig_None(rName string) string { testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerConfig_vpcRole(), testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, fmt.Sprintf(` data "aws_partition" "current" {} @@ -2034,7 +1870,10 @@ resource "aws_comprehend_entity_recognizer" "test" { aws_iam_role_policy.test, ] } +`, rName)) +} +const testAccEntityRecognizerConfig_S3_basic = ` resource "aws_s3_object" "documents" { bucket = aws_s3_bucket.test.bucket key = "documents.txt" @@ -2046,5 +1885,4 @@ resource "aws_s3_object" "entities" { key = "entitylist.csv" source = "test-fixtures/entity_recognizer/entitylist.csv" } -`, rName)) -} + ` From ebedccfa512566a32cd0b6e07dc9d2e9e0d7a9cb Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 8 Aug 2022 13:23:12 -0700 Subject: [PATCH 18/29] Adds test for specifying testing data set --- .../comprehend/entity_recognizer_test.go | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 76558998c5a..3fca78ca3e5 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -51,6 +51,8 @@ func TestAccComprehendEntityRecognizer_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "input_data_config.0.augmented_manifests.#", "0"), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.data_format", string(types.EntityRecognizerDataFormatComprehendCsv)), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.0.input_format", string(types.InputFormatOneDocPerLine)), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.0.test_s3_uri", ""), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "1"), resource.TestCheckResourceAttr(resourceName, "language_code", "en"), resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), @@ -285,6 +287,61 @@ func TestAccComprehendEntityRecognizer_versionNamePrefix(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_testDocuments(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_testDocuments(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, uniqueIDPattern()))), + resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.annotations.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.augmented_manifests.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.data_format", string(types.EntityRecognizerDataFormatComprehendCsv)), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "input_data_config.0.documents.0.test_s3_uri"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "language_code", "en"), + resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), + resource.TestCheckNoResourceAttr(resourceName, "model_policy"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), + acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", resource.UniqueIdPrefix), + resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComprehendEntityRecognizer_KMSKeys_CreateIDs(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -1018,6 +1075,45 @@ resource "aws_comprehend_entity_recognizer" "test" { `, rName, versionNamePrefix)) } +func testAccEntityRecognizerConfig_testDocuments(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_basic, + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + entity_list { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.entities.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + ] +} +`, rName)) +} + func testAccEntityRecognizerConfig_kmsKeyIds(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), From cb3d60d6d67fa41e079f50446b81025122bfab86 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 8 Aug 2022 16:03:40 -0700 Subject: [PATCH 19/29] Cleans up tests --- .../service/comprehend/entity_recognizer_test.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 3fca78ca3e5..dabc61da999 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -56,7 +56,6 @@ func TestAccComprehendEntityRecognizer_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "1"), resource.TestCheckResourceAttr(resourceName, "language_code", "en"), resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), - resource.TestCheckNoResourceAttr(resourceName, "model_policy"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), @@ -135,8 +134,6 @@ func TestAccComprehendEntityRecognizer_versionName(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "version_name", vName1), resource.TestCheckResourceAttr(resourceName, "version_name_prefix", ""), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, vName1))), - resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), - resource.TestCheckNoResourceAttr(resourceName, "model_policy"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key", "value1"), ), @@ -155,8 +152,6 @@ func TestAccComprehendEntityRecognizer_versionName(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "version_name", vName2), resource.TestCheckResourceAttr(resourceName, "version_name_prefix", ""), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, vName2))), - resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), - resource.TestCheckNoResourceAttr(resourceName, "model_policy"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key", "value2"), ), @@ -198,8 +193,6 @@ func TestAccComprehendEntityRecognizer_versionNameEmpty(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "version_name", ""), resource.TestCheckResourceAttr(resourceName, "version_name_prefix", ""), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s$`, rName))), - resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), - resource.TestCheckNoResourceAttr(resourceName, "model_policy"), ), }, { @@ -231,7 +224,7 @@ func TestAccComprehendEntityRecognizer_versionNameGenerated(t *testing.T) { CheckDestroy: testAccCheckEntityRecognizerDestroy, Steps: []resource.TestStep{ { - Config: testAccEntityRecognizerConfig_versionNameGenerated(rName), + Config: testAccEntityRecognizerConfig_versionNameNotSet(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), @@ -312,7 +305,6 @@ func TestAccComprehendEntityRecognizer_testDocuments(t *testing.T) { testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, uniqueIDPattern()))), resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), @@ -324,7 +316,6 @@ func TestAccComprehendEntityRecognizer_testDocuments(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "1"), resource.TestCheckResourceAttr(resourceName, "language_code", "en"), resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), - resource.TestCheckNoResourceAttr(resourceName, "model_policy"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), @@ -998,7 +989,7 @@ resource "aws_comprehend_entity_recognizer" "test" { `, rName)) } -func testAccEntityRecognizerConfig_versionNameGenerated(rName string) string { +func testAccEntityRecognizerConfig_versionNameNotSet(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), From 5ceec9ffa7c10cd07b6f05bbb906e0149c4b43ea Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 9 Aug 2022 15:58:12 -0700 Subject: [PATCH 20/29] Adds validation --- .../service/comprehend/entity_recognizer.go | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 8755231c7b0..5f88cfcbca9 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -78,8 +79,9 @@ func ResourceEntityRecognizer() *schema.Resource { }, }, "augmented_manifests": { - Type: schema.TypeSet, - Optional: true, + Type: schema.TypeSet, + Optional: true, + ExactlyOneOf: []string{"input_data_config.0.augmented_manifests", "input_data_config.0.documents"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "annotation_data_s3_uri": { @@ -121,9 +123,10 @@ func ResourceEntityRecognizer() *schema.Resource { Default: types.EntityRecognizerDataFormatComprehendCsv, }, "documents": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"input_data_config.0.documents", "input_data_config.0.augmented_manifests"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "input_format": { @@ -235,7 +238,31 @@ func ResourceEntityRecognizer() *schema.Resource { }, }, - CustomizeDiff: verify.SetTagsDiff, + CustomizeDiff: customdiff.All( + verify.SetTagsDiff, + func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + if diff.Id() == "" { + return nil + } + + tfMap := getInputDataConfig(diff) + if tfMap == nil { + return nil + } + + if format := types.EntityRecognizerDataFormat(tfMap["data_format"].(string)); format == types.EntityRecognizerDataFormatComprehendCsv { + if tfMap["documents"] == nil { + return fmt.Errorf("documents must be set when data_format is %s", format) + } + } else { + if tfMap["augmented_manifests"] == nil { + return fmt.Errorf("augmented_manifests must be set when data_format is %s", format) + } + } + + return nil + }, + ), } } @@ -244,7 +271,7 @@ func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, in := &comprehend.CreateEntityRecognizerInput{ DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), - InputDataConfig: expandInputDataConfig(d.Get("input_data_config").([]interface{})), + InputDataConfig: expandInputDataConfig(getInputDataConfig(d)), LanguageCode: types.LanguageCode(d.Get("language_code").(string)), RecognizerName: aws.String(d.Get("name").(string)), VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), @@ -438,7 +465,7 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, if d.HasChangesExcept("tags", "tags_all") { in := &comprehend.CreateEntityRecognizerInput{ DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), - InputDataConfig: expandInputDataConfig(d.Get("input_data_config").([]interface{})), + InputDataConfig: expandInputDataConfig(getInputDataConfig(d)), LanguageCode: types.LanguageCode(d.Get("language_code").(string)), RecognizerName: aws.String(d.Get("name").(string)), VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), @@ -930,12 +957,23 @@ func flattenVPCConfig(apiObject *types.VpcConfig) []interface{} { return []interface{}{m} } -func expandInputDataConfig(tfList []interface{}) *types.EntityRecognizerInputDataConfig { - if len(tfList) == 0 { +type resourceGetter interface { + Get(key string) any +} + +func getInputDataConfig(diff resourceGetter) map[string]any { + v := diff.Get("input_data_config").([]any) + if len(v) == 0 { return nil } - tfMap := tfList[0].(map[string]interface{}) + return v[0].(map[string]any) +} + +func expandInputDataConfig(tfMap map[string]any) *types.EntityRecognizerInputDataConfig { + if len(tfMap) == 0 { + return nil + } a := &types.EntityRecognizerInputDataConfig{ EntityTypes: expandEntityTypes(tfMap["entity_types"].(*schema.Set)), From cadaef8ed3d170f7b242a69f3b466707cdaab44e Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 9 Aug 2022 19:14:33 -0700 Subject: [PATCH 21/29] Factors out common version publishing --- .../service/comprehend/entity_recognizer.go | 404 +++++++----------- 1 file changed, 156 insertions(+), 248 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 5f88cfcbca9..3af44d06f6c 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -41,9 +41,9 @@ func ResourceEntityRecognizer() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(20 * time.Minute), - Update: schema.DefaultTimeout(20 * time.Minute), - Delete: schema.DefaultTimeout(20 * time.Minute), + Create: schema.DefaultTimeout(60 * time.Minute), + Update: schema.DefaultTimeout(60 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), }, Schema: map[string]*schema.Schema{ @@ -267,133 +267,20 @@ func ResourceEntityRecognizer() *schema.Resource { } func resourceEntityRecognizerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).ComprehendConn - - in := &comprehend.CreateEntityRecognizerInput{ - DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), - InputDataConfig: expandInputDataConfig(getInputDataConfig(d)), - LanguageCode: types.LanguageCode(d.Get("language_code").(string)), - RecognizerName: aws.String(d.Get("name").(string)), - VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), - ClientRequestToken: aws.String(resource.UniqueId()), - } - - if v, ok := d.Get("model_kms_key_id").(string); ok && v != "" { - in.ModelKmsKeyId = aws.String(v) - } - - versionName := d.GetRawConfig().GetAttr("version_name") - if versionName.IsNull() { - in.VersionName = aws.String(create.Name("", d.Get("version_name_prefix").(string))) - } else if v := versionName.AsString(); v != "" { - in.VersionName = aws.String(v) - } - - if v, ok := d.Get("volume_kms_key_id").(string); ok && v != "" { - in.VolumeKmsKeyId = aws.String(v) - } - - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - - if len(tags) > 0 { - in.Tags = Tags(tags.IgnoreAWS()) - } - - // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay - time.Sleep(iamPropagationTimeout) - - if in.VpcConfig != nil { - modelVPCENILock.Lock() - defer modelVPCENILock.Unlock() - } - - var out *comprehend.CreateEntityRecognizerOutput - err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { - var err error - out, err = conn.CreateEntityRecognizer(ctx, in) - - if err != nil { - var tmre *types.TooManyRequestsException - if errors.As(err, &tmre) { - return resource.RetryableError(err) - } else { - return resource.NonRetryableError(err) - } - } - - return nil - }) - if tfresource.TimedOut(err) { - out, err = conn.CreateEntityRecognizer(ctx, in) - } - if err != nil { - return diag.Errorf("creating Amazon Comprehend Entity Recognizer (%s): %s", d.Get("name").(string), err) - } - - if out == nil || out.EntityRecognizerArn == nil { - return diag.Errorf("creating Amazon Comprehend Entity Recognizer (%s): empty output", d.Get("name").(string)) - } - - d.SetId(aws.ToString(out.EntityRecognizerArn)) - - var g multierror.Group - waitCtx, cancel := context.WithCancel(ctx) - - g.Go(func() error { - _, err := waitEntityRecognizerCreated(waitCtx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)) - cancel() - return err - }) + awsClient := meta.(*conns.AWSClient) + conn := awsClient.ComprehendConn - var diags diag.Diagnostics - - if in.VpcConfig != nil { - g.Go(func() error { - ec2Conn := meta.(*conns.AWSClient).EC2Conn - enis, err := findNetworkInterfaces(waitCtx, ec2Conn, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets) - if err != nil { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) - return nil - } - initialENIIds := make(map[string]bool, len(enis)) - for _, v := range enis { - initialENIIds[aws.ToString(v.NetworkInterfaceId)] = true - } - - newENI, err := waitNetworkInterfaceCreated(waitCtx, ec2Conn, initialENIIds, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets, d.Timeout(schema.TimeoutCreate)) - if errors.Is(err, context.Canceled) { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), "ENI not found") - return nil - } - if err != nil { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) - return nil - } - - modelVPCENILock.Unlock() - - _, err = ec2Conn.CreateTagsWithContext(waitCtx, &ec2.CreateTagsInput{ - Resources: []*string{newENI.NetworkInterfaceId}, - Tags: []*ec2.Tag{ - { - Key: aws.String("tf-aws_comprehend_entity_recognizer"), - Value: aws.String(d.Id()), - }, - }, - }) - if err != nil { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) - return nil - } - - return nil - }) + var versionName *string + raw := d.GetRawConfig().GetAttr("version_name") + if raw.IsNull() { + versionName = aws.String(create.Name("", d.Get("version_name_prefix").(string))) + } else if v := raw.AsString(); v != "" { + versionName = aws.String(v) } - err = g.Wait().ErrorOrNil() - if err != nil { - return awsdiag.AppendErrorf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) + diags := entityRecognizerPublishVersion(ctx, conn, d, versionName, create.ErrActionCreating, d.Timeout(schema.TimeoutCreate), awsClient) + if diags.HasError() { + return diags } return append(diags, resourceEntityRecognizerRead(ctx, d, meta)...) @@ -458,134 +345,22 @@ func resourceEntityRecognizerRead(ctx context.Context, d *schema.ResourceData, m } func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).ComprehendConn + awsClient := meta.(*conns.AWSClient) + conn := awsClient.ComprehendConn var diags diag.Diagnostics if d.HasChangesExcept("tags", "tags_all") { - in := &comprehend.CreateEntityRecognizerInput{ - DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), - InputDataConfig: expandInputDataConfig(getInputDataConfig(d)), - LanguageCode: types.LanguageCode(d.Get("language_code").(string)), - RecognizerName: aws.String(d.Get("name").(string)), - VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), - ClientRequestToken: aws.String(resource.UniqueId()), - } - - if v, ok := d.Get("model_kms_key_id").(string); ok && v != "" { - in.ModelKmsKeyId = aws.String(v) - } - + var versionName *string if d.HasChange("version_name") { - in.VersionName = aws.String(d.Get("version_name").(string)) + versionName = aws.String(d.Get("version_name").(string)) } else if v := d.Get("version_name_prefix").(string); v != "" { - versionName := create.Name("", d.Get("version_name_prefix").(string)) - in.VersionName = aws.String(versionName) - } - - if v, ok := d.Get("volume_kms_key_id").(string); ok && v != "" { - in.VolumeKmsKeyId = aws.String(v) - } - - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - - if len(tags) > 0 { - in.Tags = Tags(tags.IgnoreAWS()) - } - - // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay - time.Sleep(iamPropagationTimeout) - - if in.VpcConfig != nil { - modelVPCENILock.Lock() - defer modelVPCENILock.Unlock() + versionName = aws.String(create.Name("", d.Get("version_name_prefix").(string))) } - var out *comprehend.CreateEntityRecognizerOutput - err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { - var err error - out, err = conn.CreateEntityRecognizer(ctx, in) - - if err != nil { - var tmre *types.TooManyRequestsException - if errors.As(err, &tmre) { - return resource.RetryableError(err) - } else { - return resource.NonRetryableError(err) - } - } - - return nil - }) - if tfresource.TimedOut(err) { - out, err = conn.CreateEntityRecognizer(ctx, in) - } - if err != nil { - return diag.Errorf("updating Amazon Comprehend Entity Recognizer (%s): %s", d.Id(), err) - } - - if out == nil || out.EntityRecognizerArn == nil { - return diag.Errorf("updating Amazon Comprehend Entity Recognizer (%s): empty output", d.Id()) - } - - d.SetId(aws.ToString(out.EntityRecognizerArn)) - - var g multierror.Group - waitCtx, cancel := context.WithCancel(ctx) - - g.Go(func() error { - _, err := waitEntityRecognizerCreated(waitCtx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)) - cancel() - return err - }) - - if in.VpcConfig != nil { - g.Go(func() error { - ec2Conn := meta.(*conns.AWSClient).EC2Conn - enis, err := findNetworkInterfaces(waitCtx, ec2Conn, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets) - if err != nil { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) - return nil - } - initialENIIds := make(map[string]bool, len(enis)) - for _, v := range enis { - initialENIIds[aws.ToString(v.NetworkInterfaceId)] = true - } - - newENI, err := waitNetworkInterfaceCreated(waitCtx, ec2Conn, initialENIIds, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets, d.Timeout(schema.TimeoutCreate)) - if errors.Is(err, context.Canceled) { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), "ENI not found") - return nil - } - if err != nil { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) - return nil - } - - modelVPCENILock.Unlock() - - _, err = ec2Conn.CreateTagsWithContext(waitCtx, &ec2.CreateTagsInput{ - Resources: []*string{newENI.NetworkInterfaceId}, - Tags: []*ec2.Tag{ - { - Key: aws.String("tf-aws_comprehend_entity_recognizer"), - Value: aws.String(d.Id()), - }, - }, - }) - if err != nil { - diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be created: %s", d.Id(), err) - return nil - } - - return nil - }) - } - - err = g.Wait().ErrorOrNil() - if err != nil { - return awsdiag.AppendErrorf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) to be updated: %s", d.Id(), err) + diags := entityRecognizerPublishVersion(ctx, conn, d, versionName, create.ErrActionUpdating, d.Timeout(schema.TimeoutUpdate), awsClient) + if diags.HasError() { + return diags } } else if d.HasChange("tags_all") { // For a tags-only change. If tag changes are combined with version publishing, the tags are set @@ -593,7 +368,7 @@ func resourceEntityRecognizerUpdate(ctx context.Context, d *schema.ResourceData, o, n := d.GetChange("tags_all") if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { - return diag.Errorf("updating tags for Comprehend Entity Recognizer (%s): %s", d.Id(), err) + return awsdiag.AppendErrorf(diags, "updating tags for Comprehend Entity Recognizer (%s): %s", d.Id(), err) } } @@ -699,6 +474,139 @@ func resourceEntityRecognizerDelete(ctx context.Context, d *schema.ResourceData, return nil } +func entityRecognizerPublishVersion(ctx context.Context, conn *comprehend.Client, d *schema.ResourceData, versionName *string, action string, timeout time.Duration, awsClient *conns.AWSClient) diag.Diagnostics { + in := &comprehend.CreateEntityRecognizerInput{ + DataAccessRoleArn: aws.String(d.Get("data_access_role_arn").(string)), + InputDataConfig: expandInputDataConfig(getInputDataConfig(d)), + LanguageCode: types.LanguageCode(d.Get("language_code").(string)), + RecognizerName: aws.String(d.Get("name").(string)), + VersionName: versionName, + VpcConfig: expandVPCConfig(d.Get("vpc_config").([]interface{})), + ClientRequestToken: aws.String(resource.UniqueId()), + } + + if v, ok := d.Get("model_kms_key_id").(string); ok && v != "" { + in.ModelKmsKeyId = aws.String(v) + } + + if v, ok := d.Get("volume_kms_key_id").(string); ok && v != "" { + in.VolumeKmsKeyId = aws.String(v) + } + + defaultTagsConfig := awsClient.DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } + + // Because the IAM credentials aren't evaluated until training time, we need to ensure we wait for the IAM propagation delay + time.Sleep(iamPropagationTimeout) + + if in.VpcConfig != nil { + modelVPCENILock.Lock() + defer modelVPCENILock.Unlock() + } + + var out *comprehend.CreateEntityRecognizerOutput + err := resource.RetryContext(ctx, timeout, func() *resource.RetryError { + var err error + out, err = conn.CreateEntityRecognizer(ctx, in) + + if err != nil { + var tmre *types.TooManyRequestsException + if errors.As(err, &tmre) { + return resource.RetryableError(err) + } else { + return resource.NonRetryableError(err) + } + } + + return nil + }) + if tfresource.TimedOut(err) { + out, err = conn.CreateEntityRecognizer(ctx, in) + } + if err != nil { + return diag.Errorf("%s Amazon Comprehend Entity Recognizer (%s): %s", action, d.Get("name").(string), err) + } + + if out == nil || out.EntityRecognizerArn == nil { + return diag.Errorf("%s Amazon Comprehend Entity Recognizer (%s): empty output", action, d.Get("name").(string)) + } + + d.SetId(aws.ToString(out.EntityRecognizerArn)) + + var g multierror.Group + waitCtx, cancel := context.WithCancel(ctx) + + g.Go(func() error { + _, err := waitEntityRecognizerCreated(waitCtx, conn, d.Id(), timeout) + cancel() + return err + }) + + var diags diag.Diagnostics + var tobe string + if action == create.ErrActionCreating { + tobe = "to be created" + } else if action == create.ErrActionUpdating { + tobe = "to be updated" + } else { + tobe = "to complete action" + } + + if in.VpcConfig != nil { + g.Go(func() error { + ec2Conn := awsClient.EC2Conn + enis, err := findNetworkInterfaces(waitCtx, ec2Conn, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets) + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) %s: %s", d.Id(), tobe, err) + return nil + } + initialENIIds := make(map[string]bool, len(enis)) + for _, v := range enis { + initialENIIds[aws.ToString(v.NetworkInterfaceId)] = true + } + + newENI, err := waitNetworkInterfaceCreated(waitCtx, ec2Conn, initialENIIds, in.VpcConfig.SecurityGroupIds, in.VpcConfig.Subnets, d.Timeout(schema.TimeoutCreate)) + if errors.Is(err, context.Canceled) { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) %s: %s", d.Id(), tobe, "ENI not found") + return nil + } + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) %s: %s", d.Id(), tobe, err) + return nil + } + + modelVPCENILock.Unlock() + + _, err = ec2Conn.CreateTagsWithContext(waitCtx, &ec2.CreateTagsInput{ + Resources: []*string{newENI.NetworkInterfaceId}, + Tags: []*ec2.Tag{ + { + Key: aws.String("tf-aws_comprehend_entity_recognizer"), + Value: aws.String(d.Id()), + }, + }, + }) + if err != nil { + diags = awsdiag.AppendWarningf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) %s: %s", d.Id(), tobe, err) + return nil + } + + return nil + }) + } + + err = g.Wait().ErrorOrNil() + if err != nil { + diags = awsdiag.AppendErrorf(diags, "waiting for Amazon Comprehend Entity Recognizer (%s) %s: %s", d.Id(), tobe, err) + } + + return diags +} + func FindEntityRecognizerByID(ctx context.Context, conn *comprehend.Client, id string) (*types.EntityRecognizerProperties, error) { in := &comprehend.DescribeEntityRecognizerInput{ EntityRecognizerArn: aws.String(id), From fe882deab4e027adff87645266e166ba0e530abe Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 9 Aug 2022 19:14:50 -0700 Subject: [PATCH 22/29] Fixes error with waiting for ENI detachment --- internal/service/ec2/wait.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index 198c7734f35..7148cacc6d3 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -2335,7 +2335,7 @@ func WaitNetworkInterfaceCreated(conn *ec2.EC2, id string, timeout time.Duration func WaitNetworkInterfaceDetached(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.AttachmentStatusDetaching}, + Pending: []string{ec2.AttachmentStatusAttached, ec2.AttachmentStatusDetaching}, Target: []string{ec2.AttachmentStatusDetached}, Timeout: timeout, Refresh: StatusNetworkInterfaceAttachmentStatus(ctx, conn, id), From 36387a2a84c13e86dd1163a89f2e9dff9df96fab Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 9 Aug 2022 20:26:27 -0700 Subject: [PATCH 23/29] Updates TeamCity config --- .teamcity/components/generated/services_all.kt | 2 +- internal/generate/teamcity/acctest_services.hcl | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.teamcity/components/generated/services_all.kt b/.teamcity/components/generated/services_all.kt index 51bf5a47db0..7b72ad59654 100644 --- a/.teamcity/components/generated/services_all.kt +++ b/.teamcity/components/generated/services_all.kt @@ -42,7 +42,7 @@ val services = mapOf( "codestarnotifications" to ServiceSpec("CodeStar Notifications"), "cognitoidentity" to ServiceSpec("Cognito Identity"), "cognitoidp" to ServiceSpec("Cognito IDP (Identity Provider)"), - "comprehend" to ServiceSpec("Comprehend"), + "comprehend" to ServiceSpec("Comprehend", parallelismOverride = 10), "configservice" to ServiceSpec("Config"), "connect" to ServiceSpec("Connect"), "cur" to ServiceSpec("Cost and Usage Report"), diff --git a/internal/generate/teamcity/acctest_services.hcl b/internal/generate/teamcity/acctest_services.hcl index a12527d130f..6af7df7f685 100644 --- a/internal/generate/teamcity/acctest_services.hcl +++ b/internal/generate/teamcity/acctest_services.hcl @@ -23,6 +23,10 @@ service "cloudhsmv2" { vpc_lock = true } +service "comprehend" { + parallelism = 10 +} + service "datasync" { vpc_lock = true } From 712e1cbf914f71eb10fcac869829907f90f78c9d Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 10 Aug 2022 22:33:11 -0700 Subject: [PATCH 24/29] Adds annotations and generator --- go.mod | 1 + go.sum | 2 + internal/service/comprehend/generate.go | 1 + .../entity_recognizer/annotations.csv | 1001 +++++++++ .../entity_recognizer/documents.txt | 2000 ++++++++--------- .../entity_recognizer/entitylist.csv | 1998 ++++++++-------- .../generate/entity_recognizer/main.go | 99 + 7 files changed, 3103 insertions(+), 1999 deletions(-) create mode 100644 internal/service/comprehend/test-fixtures/entity_recognizer/annotations.csv create mode 100644 internal/service/comprehend/test-fixtures/generate/entity_recognizer/main.go diff --git a/go.mod b/go.mod index 674ad31f010..2ae8a010210 100644 --- a/go.mod +++ b/go.mod @@ -94,4 +94,5 @@ require ( google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect google.golang.org/grpc v1.48.0 // indirect google.golang.org/protobuf v1.28.0 // indirect + syreclabs.com/go/faker v1.2.3 ) diff --git a/go.sum b/go.sum index a97a1ffb1ac..7b2a9f2ff72 100644 --- a/go.sum +++ b/go.sum @@ -426,3 +426,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +syreclabs.com/go/faker v1.2.3 h1:HPrWtnHazIf0/bVuPZJLFrtHlBHk10hS0SB+mV8v6R4= +syreclabs.com/go/faker v1.2.3/go.mod h1:NAXInmkPsC2xuO5MKZFe80PUXX5LU8cFdJIHGs+nSBE= diff --git a/internal/service/comprehend/generate.go b/internal/service/comprehend/generate.go index 3b46c086b6f..8bc93fe5528 100644 --- a/internal/service/comprehend/generate.go +++ b/internal/service/comprehend/generate.go @@ -1,4 +1,5 @@ //go:generate go run ../../generate/tags/main.go -ServiceTagsSlice -ListTags -UpdateTags -AWSSDKVersion=2 +//go:generate go run ./test-fixtures/generate/entity_recognizer/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. package comprehend diff --git a/internal/service/comprehend/test-fixtures/entity_recognizer/annotations.csv b/internal/service/comprehend/test-fixtures/entity_recognizer/annotations.csv new file mode 100644 index 00000000000..b60f915fc59 --- /dev/null +++ b/internal/service/comprehend/test-fixtures/entity_recognizer/annotations.csv @@ -0,0 +1,1001 @@ +File,Line,Begin Offset,End Offset,Type +documents.txt,0,19,32,MANAGER +documents.txt,1,0,15,MANAGER +documents.txt,2,0,22,MANAGER +documents.txt,3,0,14,MANAGER +documents.txt,4,0,13,ENGINEER +documents.txt,5,0,15,ENGINEER +documents.txt,6,0,14,ENGINEER +documents.txt,7,25,41,ENGINEER +documents.txt,8,0,19,MANAGER +documents.txt,9,25,38,MANAGER +documents.txt,10,0,16,MANAGER +documents.txt,11,0,12,ENGINEER +documents.txt,12,0,10,MANAGER +documents.txt,13,36,54,MANAGER +documents.txt,14,20,33,ENGINEER +documents.txt,15,0,22,MANAGER +documents.txt,16,0,14,MANAGER +documents.txt,17,0,21,MANAGER +documents.txt,18,0,15,MANAGER +documents.txt,19,0,15,MANAGER +documents.txt,20,0,14,MANAGER +documents.txt,21,20,35,ENGINEER +documents.txt,22,19,36,MANAGER +documents.txt,23,37,55,ENGINEER +documents.txt,24,0,12,MANAGER +documents.txt,25,0,12,MANAGER +documents.txt,26,19,36,MANAGER +documents.txt,27,0,13,ENGINEER +documents.txt,28,0,15,ENGINEER +documents.txt,29,0,15,MANAGER +documents.txt,30,0,17,MANAGER +documents.txt,31,0,13,MANAGER +documents.txt,32,0,13,ENGINEER +documents.txt,33,25,39,MANAGER +documents.txt,34,25,41,MANAGER +documents.txt,35,0,11,MANAGER +documents.txt,36,25,45,ENGINEER +documents.txt,37,0,15,MANAGER +documents.txt,38,0,12,MANAGER +documents.txt,39,0,14,ENGINEER +documents.txt,40,0,15,MANAGER +documents.txt,41,0,12,MANAGER +documents.txt,42,0,14,MANAGER +documents.txt,43,0,15,MANAGER +documents.txt,44,0,14,MANAGER +documents.txt,45,37,55,ENGINEER +documents.txt,46,0,14,ENGINEER +documents.txt,47,0,12,ENGINEER +documents.txt,48,20,33,ENGINEER +documents.txt,49,25,37,MANAGER +documents.txt,50,0,15,MANAGER +documents.txt,51,0,12,MANAGER +documents.txt,52,25,40,MANAGER +documents.txt,53,0,15,ENGINEER +documents.txt,54,37,51,ENGINEER +documents.txt,55,0,18,ENGINEER +documents.txt,56,25,38,MANAGER +documents.txt,57,0,18,ENGINEER +documents.txt,58,0,11,MANAGER +documents.txt,59,0,18,MANAGER +documents.txt,60,0,13,ENGINEER +documents.txt,61,19,35,MANAGER +documents.txt,62,0,14,ENGINEER +documents.txt,63,0,13,MANAGER +documents.txt,64,0,13,MANAGER +documents.txt,65,20,42,ENGINEER +documents.txt,66,25,41,MANAGER +documents.txt,67,0,22,MANAGER +documents.txt,68,25,41,ENGINEER +documents.txt,69,20,38,ENGINEER +documents.txt,70,0,11,ENGINEER +documents.txt,71,37,52,ENGINEER +documents.txt,72,0,20,ENGINEER +documents.txt,73,25,39,ENGINEER +documents.txt,74,0,11,MANAGER +documents.txt,75,0,13,ENGINEER +documents.txt,76,37,53,ENGINEER +documents.txt,77,0,17,MANAGER +documents.txt,78,0,9,MANAGER +documents.txt,79,25,43,ENGINEER +documents.txt,80,19,35,MANAGER +documents.txt,81,0,14,ENGINEER +documents.txt,82,20,30,ENGINEER +documents.txt,83,0,17,ENGINEER +documents.txt,84,36,53,MANAGER +documents.txt,85,0,20,ENGINEER +documents.txt,86,0,11,MANAGER +documents.txt,87,0,16,ENGINEER +documents.txt,88,19,35,MANAGER +documents.txt,89,0,18,MANAGER +documents.txt,90,0,12,ENGINEER +documents.txt,91,20,31,ENGINEER +documents.txt,92,20,31,ENGINEER +documents.txt,93,0,18,ENGINEER +documents.txt,94,0,13,MANAGER +documents.txt,95,25,38,MANAGER +documents.txt,96,0,18,MANAGER +documents.txt,97,37,48,ENGINEER +documents.txt,98,0,17,MANAGER +documents.txt,99,0,18,ENGINEER +documents.txt,100,0,10,MANAGER +documents.txt,101,0,15,MANAGER +documents.txt,102,0,21,ENGINEER +documents.txt,103,0,14,MANAGER +documents.txt,104,0,15,MANAGER +documents.txt,105,20,35,ENGINEER +documents.txt,106,0,16,MANAGER +documents.txt,107,0,14,ENGINEER +documents.txt,108,0,17,MANAGER +documents.txt,109,0,13,MANAGER +documents.txt,110,0,23,MANAGER +documents.txt,111,0,17,MANAGER +documents.txt,112,0,24,MANAGER +documents.txt,113,37,58,ENGINEER +documents.txt,114,0,12,ENGINEER +documents.txt,115,0,11,ENGINEER +documents.txt,116,0,14,ENGINEER +documents.txt,117,36,48,MANAGER +documents.txt,118,0,13,ENGINEER +documents.txt,119,0,13,ENGINEER +documents.txt,120,20,33,ENGINEER +documents.txt,121,0,15,MANAGER +documents.txt,122,0,19,ENGINEER +documents.txt,123,0,13,MANAGER +documents.txt,124,0,17,MANAGER +documents.txt,125,0,12,ENGINEER +documents.txt,126,0,16,MANAGER +documents.txt,127,25,42,MANAGER +documents.txt,128,25,36,ENGINEER +documents.txt,129,0,11,ENGINEER +documents.txt,130,0,15,ENGINEER +documents.txt,131,19,33,MANAGER +documents.txt,132,0,20,MANAGER +documents.txt,133,0,18,ENGINEER +documents.txt,134,37,49,ENGINEER +documents.txt,135,37,49,ENGINEER +documents.txt,136,20,37,ENGINEER +documents.txt,137,0,17,ENGINEER +documents.txt,138,0,17,ENGINEER +documents.txt,139,0,13,ENGINEER +documents.txt,140,37,57,ENGINEER +documents.txt,141,0,9,ENGINEER +documents.txt,142,0,12,MANAGER +documents.txt,143,0,10,MANAGER +documents.txt,144,0,16,MANAGER +documents.txt,145,19,30,MANAGER +documents.txt,146,19,37,MANAGER +documents.txt,147,0,12,MANAGER +documents.txt,148,0,18,MANAGER +documents.txt,149,0,12,MANAGER +documents.txt,150,0,10,ENGINEER +documents.txt,151,0,11,ENGINEER +documents.txt,152,37,48,ENGINEER +documents.txt,153,25,36,MANAGER +documents.txt,154,0,11,ENGINEER +documents.txt,155,20,35,ENGINEER +documents.txt,156,0,13,ENGINEER +documents.txt,157,0,14,MANAGER +documents.txt,158,0,18,MANAGER +documents.txt,159,0,18,MANAGER +documents.txt,160,20,34,ENGINEER +documents.txt,161,25,42,MANAGER +documents.txt,162,36,52,MANAGER +documents.txt,163,0,13,ENGINEER +documents.txt,164,0,15,MANAGER +documents.txt,165,0,12,ENGINEER +documents.txt,166,0,11,MANAGER +documents.txt,167,0,17,MANAGER +documents.txt,168,0,13,MANAGER +documents.txt,169,0,14,MANAGER +documents.txt,170,0,15,MANAGER +documents.txt,171,25,39,ENGINEER +documents.txt,172,0,17,MANAGER +documents.txt,173,0,13,MANAGER +documents.txt,174,0,14,ENGINEER +documents.txt,175,0,9,MANAGER +documents.txt,176,0,16,MANAGER +documents.txt,177,20,40,ENGINEER +documents.txt,178,0,14,ENGINEER +documents.txt,179,0,15,MANAGER +documents.txt,180,0,14,ENGINEER +documents.txt,181,0,13,MANAGER +documents.txt,182,20,36,ENGINEER +documents.txt,183,0,22,MANAGER +documents.txt,184,0,18,ENGINEER +documents.txt,185,0,17,MANAGER +documents.txt,186,0,13,MANAGER +documents.txt,187,0,15,MANAGER +documents.txt,188,36,47,MANAGER +documents.txt,189,0,20,ENGINEER +documents.txt,190,0,14,MANAGER +documents.txt,191,0,15,ENGINEER +documents.txt,192,0,18,MANAGER +documents.txt,193,0,10,MANAGER +documents.txt,194,0,18,ENGINEER +documents.txt,195,0,12,ENGINEER +documents.txt,196,25,37,MANAGER +documents.txt,197,0,10,ENGINEER +documents.txt,198,0,9,MANAGER +documents.txt,199,0,12,ENGINEER +documents.txt,200,0,16,ENGINEER +documents.txt,201,0,14,ENGINEER +documents.txt,202,0,16,MANAGER +documents.txt,203,37,53,ENGINEER +documents.txt,204,0,17,MANAGER +documents.txt,205,0,14,ENGINEER +documents.txt,206,25,45,MANAGER +documents.txt,207,0,15,MANAGER +documents.txt,208,25,42,MANAGER +documents.txt,209,19,36,MANAGER +documents.txt,210,0,15,MANAGER +documents.txt,211,0,11,MANAGER +documents.txt,212,0,12,MANAGER +documents.txt,213,36,50,MANAGER +documents.txt,214,20,43,ENGINEER +documents.txt,215,0,17,MANAGER +documents.txt,216,0,13,MANAGER +documents.txt,217,0,14,MANAGER +documents.txt,218,0,12,ENGINEER +documents.txt,219,0,13,ENGINEER +documents.txt,220,20,34,ENGINEER +documents.txt,221,0,16,ENGINEER +documents.txt,222,25,40,MANAGER +documents.txt,223,25,42,ENGINEER +documents.txt,224,25,38,MANAGER +documents.txt,225,0,14,MANAGER +documents.txt,226,0,11,MANAGER +documents.txt,227,0,12,MANAGER +documents.txt,228,0,22,ENGINEER +documents.txt,229,0,12,ENGINEER +documents.txt,230,0,15,ENGINEER +documents.txt,231,25,42,MANAGER +documents.txt,232,0,12,ENGINEER +documents.txt,233,0,11,MANAGER +documents.txt,234,36,48,MANAGER +documents.txt,235,0,14,ENGINEER +documents.txt,236,36,49,MANAGER +documents.txt,237,36,55,MANAGER +documents.txt,238,20,38,ENGINEER +documents.txt,239,0,10,ENGINEER +documents.txt,240,0,10,MANAGER +documents.txt,241,0,15,MANAGER +documents.txt,242,0,16,ENGINEER +documents.txt,243,0,15,ENGINEER +documents.txt,244,0,12,ENGINEER +documents.txt,245,0,11,ENGINEER +documents.txt,246,0,15,MANAGER +documents.txt,247,0,13,MANAGER +documents.txt,248,0,12,ENGINEER +documents.txt,249,0,14,ENGINEER +documents.txt,250,25,37,MANAGER +documents.txt,251,25,36,MANAGER +documents.txt,252,0,12,MANAGER +documents.txt,253,25,40,MANAGER +documents.txt,254,25,39,MANAGER +documents.txt,255,0,17,MANAGER +documents.txt,256,0,18,MANAGER +documents.txt,257,36,50,MANAGER +documents.txt,258,37,52,ENGINEER +documents.txt,259,0,20,ENGINEER +documents.txt,260,0,15,ENGINEER +documents.txt,261,36,48,MANAGER +documents.txt,262,0,13,ENGINEER +documents.txt,263,0,15,MANAGER +documents.txt,264,0,16,MANAGER +documents.txt,265,0,16,ENGINEER +documents.txt,266,0,17,MANAGER +documents.txt,267,36,54,MANAGER +documents.txt,268,0,15,MANAGER +documents.txt,269,0,14,ENGINEER +documents.txt,270,0,18,MANAGER +documents.txt,271,0,11,MANAGER +documents.txt,272,0,21,ENGINEER +documents.txt,273,0,21,ENGINEER +documents.txt,274,0,14,ENGINEER +documents.txt,275,0,14,MANAGER +documents.txt,276,0,18,ENGINEER +documents.txt,277,0,16,ENGINEER +documents.txt,278,0,13,MANAGER +documents.txt,279,0,15,ENGINEER +documents.txt,280,0,13,MANAGER +documents.txt,281,0,18,MANAGER +documents.txt,282,0,8,MANAGER +documents.txt,283,0,12,ENGINEER +documents.txt,284,0,15,MANAGER +documents.txt,285,36,55,MANAGER +documents.txt,286,0,16,ENGINEER +documents.txt,287,25,40,MANAGER +documents.txt,288,19,33,MANAGER +documents.txt,289,0,14,ENGINEER +documents.txt,290,0,13,ENGINEER +documents.txt,291,19,33,MANAGER +documents.txt,292,0,13,MANAGER +documents.txt,293,25,37,ENGINEER +documents.txt,294,0,16,ENGINEER +documents.txt,295,0,25,ENGINEER +documents.txt,296,0,17,ENGINEER +documents.txt,297,37,52,ENGINEER +documents.txt,298,37,53,ENGINEER +documents.txt,299,0,13,MANAGER +documents.txt,300,20,39,ENGINEER +documents.txt,301,0,15,MANAGER +documents.txt,302,0,15,MANAGER +documents.txt,303,0,12,MANAGER +documents.txt,304,0,22,MANAGER +documents.txt,305,25,42,MANAGER +documents.txt,306,0,13,ENGINEER +documents.txt,307,25,43,MANAGER +documents.txt,308,37,55,ENGINEER +documents.txt,309,0,19,MANAGER +documents.txt,310,36,53,MANAGER +documents.txt,311,0,13,ENGINEER +documents.txt,312,0,12,ENGINEER +documents.txt,313,0,12,MANAGER +documents.txt,314,36,49,MANAGER +documents.txt,315,0,18,ENGINEER +documents.txt,316,0,14,ENGINEER +documents.txt,317,0,13,MANAGER +documents.txt,318,25,37,ENGINEER +documents.txt,319,37,49,ENGINEER +documents.txt,320,0,21,ENGINEER +documents.txt,321,0,11,MANAGER +documents.txt,322,0,14,ENGINEER +documents.txt,323,25,44,MANAGER +documents.txt,324,0,13,MANAGER +documents.txt,325,25,35,ENGINEER +documents.txt,326,25,41,MANAGER +documents.txt,327,0,12,ENGINEER +documents.txt,328,0,11,MANAGER +documents.txt,329,0,22,MANAGER +documents.txt,330,0,19,MANAGER +documents.txt,331,0,13,ENGINEER +documents.txt,332,0,15,MANAGER +documents.txt,333,0,13,MANAGER +documents.txt,334,0,13,ENGINEER +documents.txt,335,25,46,MANAGER +documents.txt,336,19,39,MANAGER +documents.txt,337,0,16,ENGINEER +documents.txt,338,0,12,ENGINEER +documents.txt,339,0,11,ENGINEER +documents.txt,340,20,34,ENGINEER +documents.txt,341,0,13,MANAGER +documents.txt,342,0,11,MANAGER +documents.txt,343,0,12,MANAGER +documents.txt,344,0,16,MANAGER +documents.txt,345,0,13,MANAGER +documents.txt,346,0,15,MANAGER +documents.txt,347,0,17,MANAGER +documents.txt,348,37,52,ENGINEER +documents.txt,349,0,12,MANAGER +documents.txt,350,36,50,MANAGER +documents.txt,351,0,12,ENGINEER +documents.txt,352,0,13,ENGINEER +documents.txt,353,0,11,MANAGER +documents.txt,354,36,49,MANAGER +documents.txt,355,0,13,ENGINEER +documents.txt,356,0,18,ENGINEER +documents.txt,357,37,55,ENGINEER +documents.txt,358,20,31,ENGINEER +documents.txt,359,0,20,ENGINEER +documents.txt,360,0,15,ENGINEER +documents.txt,361,0,14,MANAGER +documents.txt,362,0,13,MANAGER +documents.txt,363,19,34,MANAGER +documents.txt,364,0,12,MANAGER +documents.txt,365,0,13,MANAGER +documents.txt,366,0,14,MANAGER +documents.txt,367,0,17,ENGINEER +documents.txt,368,0,13,MANAGER +documents.txt,369,0,17,ENGINEER +documents.txt,370,37,50,ENGINEER +documents.txt,371,0,19,MANAGER +documents.txt,372,25,42,ENGINEER +documents.txt,373,0,14,MANAGER +documents.txt,374,25,38,ENGINEER +documents.txt,375,0,13,ENGINEER +documents.txt,376,0,11,MANAGER +documents.txt,377,0,12,ENGINEER +documents.txt,378,25,40,MANAGER +documents.txt,379,0,15,ENGINEER +documents.txt,380,0,11,ENGINEER +documents.txt,381,0,20,MANAGER +documents.txt,382,0,19,ENGINEER +documents.txt,383,0,24,ENGINEER +documents.txt,384,0,12,ENGINEER +documents.txt,385,0,16,MANAGER +documents.txt,386,0,14,MANAGER +documents.txt,387,0,9,MANAGER +documents.txt,388,0,12,MANAGER +documents.txt,389,36,50,MANAGER +documents.txt,390,20,32,ENGINEER +documents.txt,391,0,15,ENGINEER +documents.txt,392,0,17,MANAGER +documents.txt,393,19,34,MANAGER +documents.txt,394,0,17,ENGINEER +documents.txt,395,0,16,ENGINEER +documents.txt,396,0,16,MANAGER +documents.txt,397,0,20,ENGINEER +documents.txt,398,0,17,MANAGER +documents.txt,399,0,16,MANAGER +documents.txt,400,0,12,ENGINEER +documents.txt,401,0,10,MANAGER +documents.txt,402,0,14,MANAGER +documents.txt,403,0,11,ENGINEER +documents.txt,404,25,35,ENGINEER +documents.txt,405,0,13,ENGINEER +documents.txt,406,0,13,MANAGER +documents.txt,407,36,47,MANAGER +documents.txt,408,20,31,ENGINEER +documents.txt,409,37,47,ENGINEER +documents.txt,410,0,13,ENGINEER +documents.txt,411,20,34,ENGINEER +documents.txt,412,0,19,MANAGER +documents.txt,413,0,12,ENGINEER +documents.txt,414,0,14,ENGINEER +documents.txt,415,0,14,MANAGER +documents.txt,416,20,31,ENGINEER +documents.txt,417,0,13,MANAGER +documents.txt,418,0,17,ENGINEER +documents.txt,419,0,12,ENGINEER +documents.txt,420,0,13,ENGINEER +documents.txt,421,0,15,MANAGER +documents.txt,422,19,37,MANAGER +documents.txt,423,37,50,ENGINEER +documents.txt,424,0,17,MANAGER +documents.txt,425,0,15,ENGINEER +documents.txt,426,0,9,ENGINEER +documents.txt,427,0,11,MANAGER +documents.txt,428,0,13,MANAGER +documents.txt,429,0,13,MANAGER +documents.txt,430,0,17,ENGINEER +documents.txt,431,20,39,ENGINEER +documents.txt,432,19,32,MANAGER +documents.txt,433,0,19,MANAGER +documents.txt,434,25,40,ENGINEER +documents.txt,435,0,14,MANAGER +documents.txt,436,0,16,ENGINEER +documents.txt,437,0,24,ENGINEER +documents.txt,438,19,38,MANAGER +documents.txt,439,0,17,MANAGER +documents.txt,440,36,52,MANAGER +documents.txt,441,0,16,ENGINEER +documents.txt,442,0,20,MANAGER +documents.txt,443,0,11,MANAGER +documents.txt,444,0,12,MANAGER +documents.txt,445,0,16,MANAGER +documents.txt,446,0,13,MANAGER +documents.txt,447,0,15,MANAGER +documents.txt,448,0,14,ENGINEER +documents.txt,449,0,17,ENGINEER +documents.txt,450,0,13,MANAGER +documents.txt,451,0,14,ENGINEER +documents.txt,452,0,12,MANAGER +documents.txt,453,0,9,MANAGER +documents.txt,454,0,18,MANAGER +documents.txt,455,25,42,ENGINEER +documents.txt,456,0,20,MANAGER +documents.txt,457,0,15,ENGINEER +documents.txt,458,37,47,ENGINEER +documents.txt,459,0,23,ENGINEER +documents.txt,460,0,13,MANAGER +documents.txt,461,19,35,MANAGER +documents.txt,462,0,10,ENGINEER +documents.txt,463,0,15,MANAGER +documents.txt,464,0,12,MANAGER +documents.txt,465,0,11,MANAGER +documents.txt,466,25,38,ENGINEER +documents.txt,467,25,41,MANAGER +documents.txt,468,25,44,MANAGER +documents.txt,469,25,41,MANAGER +documents.txt,470,0,18,MANAGER +documents.txt,471,0,10,ENGINEER +documents.txt,472,0,15,ENGINEER +documents.txt,473,0,17,MANAGER +documents.txt,474,19,40,MANAGER +documents.txt,475,0,15,ENGINEER +documents.txt,476,19,35,MANAGER +documents.txt,477,0,14,MANAGER +documents.txt,478,0,18,MANAGER +documents.txt,479,0,15,ENGINEER +documents.txt,480,25,47,MANAGER +documents.txt,481,20,32,ENGINEER +documents.txt,482,0,13,ENGINEER +documents.txt,483,0,24,ENGINEER +documents.txt,484,0,16,ENGINEER +documents.txt,485,0,13,MANAGER +documents.txt,486,0,20,MANAGER +documents.txt,487,0,15,MANAGER +documents.txt,488,0,15,ENGINEER +documents.txt,489,0,11,ENGINEER +documents.txt,490,0,10,MANAGER +documents.txt,491,25,38,ENGINEER +documents.txt,492,0,12,ENGINEER +documents.txt,493,0,15,MANAGER +documents.txt,494,0,12,MANAGER +documents.txt,495,0,11,ENGINEER +documents.txt,496,0,15,MANAGER +documents.txt,497,0,15,ENGINEER +documents.txt,498,25,41,ENGINEER +documents.txt,499,25,38,ENGINEER +documents.txt,500,0,14,MANAGER +documents.txt,501,25,37,MANAGER +documents.txt,502,0,14,MANAGER +documents.txt,503,25,40,ENGINEER +documents.txt,504,0,10,ENGINEER +documents.txt,505,0,17,MANAGER +documents.txt,506,0,16,MANAGER +documents.txt,507,0,10,ENGINEER +documents.txt,508,0,12,MANAGER +documents.txt,509,0,13,ENGINEER +documents.txt,510,0,12,ENGINEER +documents.txt,511,25,38,ENGINEER +documents.txt,512,0,10,MANAGER +documents.txt,513,0,13,MANAGER +documents.txt,514,0,14,ENGINEER +documents.txt,515,0,15,MANAGER +documents.txt,516,0,17,MANAGER +documents.txt,517,20,32,ENGINEER +documents.txt,518,0,15,ENGINEER +documents.txt,519,0,14,ENGINEER +documents.txt,520,19,39,MANAGER +documents.txt,521,0,15,MANAGER +documents.txt,522,0,17,ENGINEER +documents.txt,523,0,15,ENGINEER +documents.txt,524,0,17,MANAGER +documents.txt,525,0,12,MANAGER +documents.txt,526,0,11,MANAGER +documents.txt,527,20,32,ENGINEER +documents.txt,528,0,16,ENGINEER +documents.txt,529,0,18,ENGINEER +documents.txt,530,20,35,ENGINEER +documents.txt,531,0,14,ENGINEER +documents.txt,532,0,18,MANAGER +documents.txt,533,0,16,ENGINEER +documents.txt,534,0,8,ENGINEER +documents.txt,535,0,14,MANAGER +documents.txt,536,25,47,ENGINEER +documents.txt,537,0,16,ENGINEER +documents.txt,538,20,34,ENGINEER +documents.txt,539,0,14,MANAGER +documents.txt,540,0,11,MANAGER +documents.txt,541,25,37,MANAGER +documents.txt,542,0,13,MANAGER +documents.txt,543,0,16,ENGINEER +documents.txt,544,0,10,MANAGER +documents.txt,545,0,16,ENGINEER +documents.txt,546,0,17,MANAGER +documents.txt,547,25,42,MANAGER +documents.txt,548,0,12,MANAGER +documents.txt,549,0,17,MANAGER +documents.txt,550,0,12,ENGINEER +documents.txt,551,0,19,MANAGER +documents.txt,552,0,15,ENGINEER +documents.txt,553,0,16,MANAGER +documents.txt,554,20,35,ENGINEER +documents.txt,555,0,20,MANAGER +documents.txt,556,0,15,MANAGER +documents.txt,557,0,15,MANAGER +documents.txt,558,0,17,MANAGER +documents.txt,559,0,17,ENGINEER +documents.txt,560,19,33,MANAGER +documents.txt,561,36,44,MANAGER +documents.txt,562,0,13,MANAGER +documents.txt,563,0,15,MANAGER +documents.txt,564,0,13,MANAGER +documents.txt,565,0,21,ENGINEER +documents.txt,566,0,22,ENGINEER +documents.txt,567,19,36,MANAGER +documents.txt,568,20,33,ENGINEER +documents.txt,569,0,14,MANAGER +documents.txt,570,0,16,ENGINEER +documents.txt,571,0,13,ENGINEER +documents.txt,572,0,18,ENGINEER +documents.txt,573,0,10,ENGINEER +documents.txt,574,0,13,MANAGER +documents.txt,575,0,15,MANAGER +documents.txt,576,0,17,ENGINEER +documents.txt,577,0,17,ENGINEER +documents.txt,578,0,14,ENGINEER +documents.txt,579,0,13,MANAGER +documents.txt,580,0,17,ENGINEER +documents.txt,581,37,48,ENGINEER +documents.txt,582,0,16,ENGINEER +documents.txt,583,37,55,ENGINEER +documents.txt,584,0,11,MANAGER +documents.txt,585,37,55,ENGINEER +documents.txt,586,0,20,MANAGER +documents.txt,587,0,13,MANAGER +documents.txt,588,0,13,MANAGER +documents.txt,589,25,35,ENGINEER +documents.txt,590,0,15,ENGINEER +documents.txt,591,0,21,MANAGER +documents.txt,592,0,13,MANAGER +documents.txt,593,0,12,ENGINEER +documents.txt,594,0,16,ENGINEER +documents.txt,595,20,34,ENGINEER +documents.txt,596,20,34,ENGINEER +documents.txt,597,0,14,ENGINEER +documents.txt,598,0,17,MANAGER +documents.txt,599,0,13,MANAGER +documents.txt,600,0,20,MANAGER +documents.txt,601,0,16,MANAGER +documents.txt,602,0,18,MANAGER +documents.txt,603,0,18,MANAGER +documents.txt,604,0,19,MANAGER +documents.txt,605,0,19,MANAGER +documents.txt,606,0,12,ENGINEER +documents.txt,607,25,43,ENGINEER +documents.txt,608,0,22,MANAGER +documents.txt,609,0,12,MANAGER +documents.txt,610,36,53,MANAGER +documents.txt,611,0,12,ENGINEER +documents.txt,612,0,16,MANAGER +documents.txt,613,0,14,MANAGER +documents.txt,614,0,13,ENGINEER +documents.txt,615,0,17,ENGINEER +documents.txt,616,25,39,ENGINEER +documents.txt,617,0,13,ENGINEER +documents.txt,618,0,19,ENGINEER +documents.txt,619,0,15,MANAGER +documents.txt,620,0,20,MANAGER +documents.txt,621,0,15,MANAGER +documents.txt,622,0,14,ENGINEER +documents.txt,623,0,15,MANAGER +documents.txt,624,37,51,ENGINEER +documents.txt,625,0,19,ENGINEER +documents.txt,626,0,19,ENGINEER +documents.txt,627,0,13,MANAGER +documents.txt,628,0,17,MANAGER +documents.txt,629,0,22,MANAGER +documents.txt,630,0,14,ENGINEER +documents.txt,631,0,21,ENGINEER +documents.txt,632,0,12,ENGINEER +documents.txt,633,0,16,ENGINEER +documents.txt,634,0,13,ENGINEER +documents.txt,635,36,49,MANAGER +documents.txt,636,0,10,MANAGER +documents.txt,637,0,24,MANAGER +documents.txt,638,19,36,MANAGER +documents.txt,639,0,18,ENGINEER +documents.txt,640,0,15,MANAGER +documents.txt,641,0,19,ENGINEER +documents.txt,642,0,17,ENGINEER +documents.txt,643,0,13,ENGINEER +documents.txt,644,0,18,ENGINEER +documents.txt,645,0,17,ENGINEER +documents.txt,646,19,29,MANAGER +documents.txt,647,0,14,ENGINEER +documents.txt,648,0,16,MANAGER +documents.txt,649,0,14,MANAGER +documents.txt,650,0,14,MANAGER +documents.txt,651,0,19,MANAGER +documents.txt,652,0,10,ENGINEER +documents.txt,653,0,18,MANAGER +documents.txt,654,0,16,ENGINEER +documents.txt,655,0,12,MANAGER +documents.txt,656,0,20,ENGINEER +documents.txt,657,19,40,MANAGER +documents.txt,658,0,12,MANAGER +documents.txt,659,0,20,MANAGER +documents.txt,660,25,39,MANAGER +documents.txt,661,0,18,MANAGER +documents.txt,662,0,16,ENGINEER +documents.txt,663,25,33,MANAGER +documents.txt,664,0,10,MANAGER +documents.txt,665,25,38,ENGINEER +documents.txt,666,0,11,MANAGER +documents.txt,667,0,17,ENGINEER +documents.txt,668,36,55,MANAGER +documents.txt,669,0,11,MANAGER +documents.txt,670,0,18,ENGINEER +documents.txt,671,0,13,ENGINEER +documents.txt,672,25,45,MANAGER +documents.txt,673,0,13,MANAGER +documents.txt,674,0,16,MANAGER +documents.txt,675,0,18,ENGINEER +documents.txt,676,0,13,ENGINEER +documents.txt,677,37,56,ENGINEER +documents.txt,678,0,16,MANAGER +documents.txt,679,0,18,MANAGER +documents.txt,680,0,17,MANAGER +documents.txt,681,25,39,MANAGER +documents.txt,682,0,14,MANAGER +documents.txt,683,0,11,MANAGER +documents.txt,684,0,12,ENGINEER +documents.txt,685,0,20,ENGINEER +documents.txt,686,0,16,MANAGER +documents.txt,687,0,11,ENGINEER +documents.txt,688,0,9,ENGINEER +documents.txt,689,0,18,ENGINEER +documents.txt,690,0,16,MANAGER +documents.txt,691,0,15,ENGINEER +documents.txt,692,0,14,ENGINEER +documents.txt,693,0,16,ENGINEER +documents.txt,694,0,14,MANAGER +documents.txt,695,0,12,MANAGER +documents.txt,696,0,15,ENGINEER +documents.txt,697,0,8,ENGINEER +documents.txt,698,0,11,MANAGER +documents.txt,699,0,19,ENGINEER +documents.txt,700,25,41,MANAGER +documents.txt,701,0,14,MANAGER +documents.txt,702,0,13,ENGINEER +documents.txt,703,0,15,MANAGER +documents.txt,704,25,39,ENGINEER +documents.txt,705,0,10,MANAGER +documents.txt,706,0,15,MANAGER +documents.txt,707,25,36,ENGINEER +documents.txt,708,0,14,MANAGER +documents.txt,709,0,12,MANAGER +documents.txt,710,0,14,ENGINEER +documents.txt,711,36,45,MANAGER +documents.txt,712,0,15,MANAGER +documents.txt,713,0,13,ENGINEER +documents.txt,714,25,37,MANAGER +documents.txt,715,0,10,ENGINEER +documents.txt,716,0,12,MANAGER +documents.txt,717,0,12,MANAGER +documents.txt,718,0,17,ENGINEER +documents.txt,719,19,34,MANAGER +documents.txt,720,0,16,ENGINEER +documents.txt,721,0,18,ENGINEER +documents.txt,722,0,16,MANAGER +documents.txt,723,37,56,ENGINEER +documents.txt,724,36,56,MANAGER +documents.txt,725,0,17,ENGINEER +documents.txt,726,0,21,MANAGER +documents.txt,727,0,13,ENGINEER +documents.txt,728,0,14,ENGINEER +documents.txt,729,0,11,MANAGER +documents.txt,730,0,13,MANAGER +documents.txt,731,0,16,ENGINEER +documents.txt,732,0,17,MANAGER +documents.txt,733,25,39,MANAGER +documents.txt,734,0,17,MANAGER +documents.txt,735,36,48,MANAGER +documents.txt,736,0,11,MANAGER +documents.txt,737,0,16,MANAGER +documents.txt,738,0,16,ENGINEER +documents.txt,739,0,16,MANAGER +documents.txt,740,0,14,MANAGER +documents.txt,741,0,16,ENGINEER +documents.txt,742,0,17,ENGINEER +documents.txt,743,25,44,ENGINEER +documents.txt,744,25,38,ENGINEER +documents.txt,745,0,14,ENGINEER +documents.txt,746,19,32,MANAGER +documents.txt,747,0,11,ENGINEER +documents.txt,748,0,21,ENGINEER +documents.txt,749,0,16,ENGINEER +documents.txt,750,0,18,ENGINEER +documents.txt,751,0,11,MANAGER +documents.txt,752,0,10,ENGINEER +documents.txt,753,0,14,ENGINEER +documents.txt,754,0,17,MANAGER +documents.txt,755,0,16,ENGINEER +documents.txt,756,0,13,MANAGER +documents.txt,757,0,18,ENGINEER +documents.txt,758,0,15,MANAGER +documents.txt,759,0,13,ENGINEER +documents.txt,760,0,10,MANAGER +documents.txt,761,0,14,ENGINEER +documents.txt,762,25,39,MANAGER +documents.txt,763,37,54,ENGINEER +documents.txt,764,0,12,MANAGER +documents.txt,765,0,14,MANAGER +documents.txt,766,0,19,MANAGER +documents.txt,767,0,18,MANAGER +documents.txt,768,20,37,ENGINEER +documents.txt,769,0,14,MANAGER +documents.txt,770,25,38,MANAGER +documents.txt,771,0,15,ENGINEER +documents.txt,772,0,13,ENGINEER +documents.txt,773,0,14,MANAGER +documents.txt,774,25,39,MANAGER +documents.txt,775,0,18,MANAGER +documents.txt,776,0,17,MANAGER +documents.txt,777,0,14,MANAGER +documents.txt,778,19,33,MANAGER +documents.txt,779,0,16,MANAGER +documents.txt,780,0,10,ENGINEER +documents.txt,781,0,11,ENGINEER +documents.txt,782,20,37,ENGINEER +documents.txt,783,37,49,ENGINEER +documents.txt,784,0,14,ENGINEER +documents.txt,785,0,10,ENGINEER +documents.txt,786,36,48,MANAGER +documents.txt,787,19,30,MANAGER +documents.txt,788,0,14,ENGINEER +documents.txt,789,0,16,MANAGER +documents.txt,790,0,23,ENGINEER +documents.txt,791,25,38,MANAGER +documents.txt,792,37,53,ENGINEER +documents.txt,793,0,12,ENGINEER +documents.txt,794,36,51,MANAGER +documents.txt,795,0,17,MANAGER +documents.txt,796,0,17,ENGINEER +documents.txt,797,0,18,ENGINEER +documents.txt,798,0,15,MANAGER +documents.txt,799,0,11,ENGINEER +documents.txt,800,0,14,MANAGER +documents.txt,801,0,12,ENGINEER +documents.txt,802,0,11,ENGINEER +documents.txt,803,0,15,MANAGER +documents.txt,804,0,13,ENGINEER +documents.txt,805,0,14,MANAGER +documents.txt,806,20,35,ENGINEER +documents.txt,807,25,36,ENGINEER +documents.txt,808,0,15,MANAGER +documents.txt,809,0,12,ENGINEER +documents.txt,810,37,51,ENGINEER +documents.txt,811,0,10,ENGINEER +documents.txt,812,0,12,MANAGER +documents.txt,813,20,31,ENGINEER +documents.txt,814,0,17,ENGINEER +documents.txt,815,0,10,ENGINEER +documents.txt,816,19,32,MANAGER +documents.txt,817,0,16,MANAGER +documents.txt,818,0,11,MANAGER +documents.txt,819,0,14,ENGINEER +documents.txt,820,0,19,MANAGER +documents.txt,821,0,13,ENGINEER +documents.txt,822,0,16,MANAGER +documents.txt,823,0,14,MANAGER +documents.txt,824,0,14,MANAGER +documents.txt,825,0,13,ENGINEER +documents.txt,826,0,14,ENGINEER +documents.txt,827,0,10,ENGINEER +documents.txt,828,0,11,MANAGER +documents.txt,829,0,15,ENGINEER +documents.txt,830,0,10,MANAGER +documents.txt,831,0,17,MANAGER +documents.txt,832,0,19,MANAGER +documents.txt,833,0,15,ENGINEER +documents.txt,834,0,20,ENGINEER +documents.txt,835,0,15,MANAGER +documents.txt,836,0,16,MANAGER +documents.txt,837,0,11,MANAGER +documents.txt,838,0,13,MANAGER +documents.txt,839,0,14,MANAGER +documents.txt,840,0,15,ENGINEER +documents.txt,841,0,16,MANAGER +documents.txt,842,25,43,ENGINEER +documents.txt,843,0,17,MANAGER +documents.txt,844,0,15,ENGINEER +documents.txt,845,0,14,MANAGER +documents.txt,846,0,16,ENGINEER +documents.txt,847,0,10,ENGINEER +documents.txt,848,0,11,ENGINEER +documents.txt,849,36,53,MANAGER +documents.txt,850,0,21,MANAGER +documents.txt,851,0,15,MANAGER +documents.txt,852,0,13,MANAGER +documents.txt,853,0,11,ENGINEER +documents.txt,854,0,22,MANAGER +documents.txt,855,0,13,MANAGER +documents.txt,856,0,12,MANAGER +documents.txt,857,0,16,ENGINEER +documents.txt,858,0,18,ENGINEER +documents.txt,859,0,9,ENGINEER +documents.txt,860,0,12,ENGINEER +documents.txt,861,0,14,ENGINEER +documents.txt,862,37,55,ENGINEER +documents.txt,863,0,12,MANAGER +documents.txt,864,0,15,ENGINEER +documents.txt,865,0,13,ENGINEER +documents.txt,866,0,14,ENGINEER +documents.txt,867,20,32,ENGINEER +documents.txt,868,37,50,ENGINEER +documents.txt,869,0,10,ENGINEER +documents.txt,870,0,13,MANAGER +documents.txt,871,0,16,ENGINEER +documents.txt,872,0,14,ENGINEER +documents.txt,873,0,16,MANAGER +documents.txt,874,25,40,ENGINEER +documents.txt,875,0,14,ENGINEER +documents.txt,876,36,49,MANAGER +documents.txt,877,0,9,MANAGER +documents.txt,878,0,17,MANAGER +documents.txt,879,25,39,MANAGER +documents.txt,880,0,14,MANAGER +documents.txt,881,37,57,ENGINEER +documents.txt,882,0,12,MANAGER +documents.txt,883,19,34,MANAGER +documents.txt,884,0,18,ENGINEER +documents.txt,885,0,16,ENGINEER +documents.txt,886,37,50,ENGINEER +documents.txt,887,0,20,ENGINEER +documents.txt,888,0,16,ENGINEER +documents.txt,889,0,19,MANAGER +documents.txt,890,0,11,MANAGER +documents.txt,891,0,10,MANAGER +documents.txt,892,0,17,ENGINEER +documents.txt,893,0,19,ENGINEER +documents.txt,894,0,9,MANAGER +documents.txt,895,0,14,ENGINEER +documents.txt,896,0,14,MANAGER +documents.txt,897,0,10,ENGINEER +documents.txt,898,0,20,ENGINEER +documents.txt,899,0,13,ENGINEER +documents.txt,900,0,24,ENGINEER +documents.txt,901,0,11,MANAGER +documents.txt,902,0,18,ENGINEER +documents.txt,903,0,19,MANAGER +documents.txt,904,37,52,ENGINEER +documents.txt,905,0,17,MANAGER +documents.txt,906,0,14,ENGINEER +documents.txt,907,0,13,MANAGER +documents.txt,908,0,16,ENGINEER +documents.txt,909,0,13,ENGINEER +documents.txt,910,0,11,ENGINEER +documents.txt,911,0,12,ENGINEER +documents.txt,912,0,16,ENGINEER +documents.txt,913,0,12,ENGINEER +documents.txt,914,36,50,MANAGER +documents.txt,915,0,15,ENGINEER +documents.txt,916,0,16,ENGINEER +documents.txt,917,36,50,MANAGER +documents.txt,918,0,12,MANAGER +documents.txt,919,0,14,ENGINEER +documents.txt,920,0,15,MANAGER +documents.txt,921,0,13,ENGINEER +documents.txt,922,20,36,ENGINEER +documents.txt,923,0,18,ENGINEER +documents.txt,924,19,37,MANAGER +documents.txt,925,0,16,ENGINEER +documents.txt,926,0,14,ENGINEER +documents.txt,927,25,36,ENGINEER +documents.txt,928,37,49,ENGINEER +documents.txt,929,0,10,MANAGER +documents.txt,930,0,10,ENGINEER +documents.txt,931,0,14,ENGINEER +documents.txt,932,0,15,ENGINEER +documents.txt,933,0,16,MANAGER +documents.txt,934,0,14,ENGINEER +documents.txt,935,0,23,ENGINEER +documents.txt,936,0,18,ENGINEER +documents.txt,937,0,18,MANAGER +documents.txt,938,0,16,ENGINEER +documents.txt,939,36,49,MANAGER +documents.txt,940,0,15,ENGINEER +documents.txt,941,0,13,ENGINEER +documents.txt,942,0,13,MANAGER +documents.txt,943,25,39,ENGINEER +documents.txt,944,37,54,ENGINEER +documents.txt,945,0,12,MANAGER +documents.txt,946,0,13,ENGINEER +documents.txt,947,0,18,MANAGER +documents.txt,948,20,36,ENGINEER +documents.txt,949,0,11,ENGINEER +documents.txt,950,0,14,ENGINEER +documents.txt,951,0,15,ENGINEER +documents.txt,952,0,16,MANAGER +documents.txt,953,0,10,ENGINEER +documents.txt,954,0,18,ENGINEER +documents.txt,955,0,15,MANAGER +documents.txt,956,25,37,MANAGER +documents.txt,957,0,15,ENGINEER +documents.txt,958,0,10,MANAGER +documents.txt,959,0,14,ENGINEER +documents.txt,960,0,18,ENGINEER +documents.txt,961,0,14,MANAGER +documents.txt,962,0,14,MANAGER +documents.txt,963,0,14,MANAGER +documents.txt,964,0,12,ENGINEER +documents.txt,965,0,19,MANAGER +documents.txt,966,0,17,ENGINEER +documents.txt,967,0,12,ENGINEER +documents.txt,968,0,15,MANAGER +documents.txt,969,0,17,ENGINEER +documents.txt,970,0,11,MANAGER +documents.txt,971,25,39,ENGINEER +documents.txt,972,0,12,ENGINEER +documents.txt,973,19,33,MANAGER +documents.txt,974,19,32,MANAGER +documents.txt,975,0,23,MANAGER +documents.txt,976,20,44,ENGINEER +documents.txt,977,0,13,ENGINEER +documents.txt,978,0,15,MANAGER +documents.txt,979,0,19,ENGINEER +documents.txt,980,0,12,MANAGER +documents.txt,981,25,40,MANAGER +documents.txt,982,0,12,ENGINEER +documents.txt,983,0,13,MANAGER +documents.txt,984,0,12,MANAGER +documents.txt,985,37,53,ENGINEER +documents.txt,986,25,38,ENGINEER +documents.txt,987,0,11,ENGINEER +documents.txt,988,37,50,ENGINEER +documents.txt,989,0,18,ENGINEER +documents.txt,990,20,35,ENGINEER +documents.txt,991,19,33,MANAGER +documents.txt,992,0,18,MANAGER +documents.txt,993,0,17,MANAGER +documents.txt,994,0,11,MANAGER +documents.txt,995,0,13,MANAGER +documents.txt,996,0,14,MANAGER +documents.txt,997,0,13,MANAGER +documents.txt,998,0,17,MANAGER +documents.txt,999,25,37,ENGINEER diff --git a/internal/service/comprehend/test-fixtures/entity_recognizer/documents.txt b/internal/service/comprehend/test-fixtures/entity_recognizer/documents.txt index 1c5462f82b6..f68319e5bb7 100644 --- a/internal/service/comprehend/test-fixtures/entity_recognizer/documents.txt +++ b/internal/service/comprehend/test-fixtures/entity_recognizer/documents.txt @@ -1,1000 +1,1000 @@ -Announcing manager Rachelle Lubowitz MD. -Donnell Schulist is a manager. -Adelbert Ankunding has been an manager for over a decade. -Mr. Francesca Moen will be the new manager for the team. -Shanna Roberts is a engineer in the high tech industry. -Rachel Williamson is a engineer with Example Corp. -Tianna Keebler PhD is retiring as a engineer. -Our latest new employee, Rowena Sipes III, has been a engineer in the industry for 4 years. -Mortimer Mueller Sr. is retiring as a manager. -Our latest new employee, Mack Bashirian, has been a manager in the industry for 4 years. -Mohammad Bayer I has been an manager for over a decade. -Fleta Raynor DDS joins us as an engineer on the Example project. -Duane Kassulke is a manager with Example Corp. -Help me welcome our newest manager, Clarissa Jenkins. -Announcing engineer Chester Bradtke. -Alexandrine Watsica is a manager in the high tech industry. -Mr. Donnie Osinski is a manager. -Dudley Jacobs DDS will be the new manager for the team. -Katrina Senger is a manager with Example Corp. -Andy Hammes DVM has been an manager for over a decade. -Jaylan Klocko joins us as an manager on the Example project. -Announcing engineer Mr. Kaleigh Champlin. -Announcing manager Nick Predovic. -Help me welcome our newest engineer, Miss Nichole Farrell. -Elliot Ziemann, an manager, will be presenting the award. -Mr. Zoila Howell has been a manager for 14 years. -Announcing manager Max Mertz. -Edwardo Nolan V has been a engineer for 14 years. -Mya McGlynn has been an engineer for over a decade. -Annette Sauer is a manager. -Rocky Heathcote has been an manager for over a decade. -Alize Collier is a manager in the high tech industry. -Lea Romaguera will be the new engineer for the team. -Our latest new employee, Jacinto Simonis PhD, has been a manager in the industry for 4 years. -Our latest new employee, Marques Morar, has been a manager in the industry for 4 years. -Enoch Grant is a manager in the high tech industry. -Our latest new employee, Liam Beer, has been a engineer in the industry for 4 years. -Dr. Tad Hilpert has been an manager for over a decade. -Rhianna Lang is a manager with Example Corp. -Mr. Florian White will be the new engineer for the team. -Helmer O'Connell, an manager, will be presenting the award. -Raoul Strosin has been a manager for 14 years. -Deshaun Wilkinson is a manager with Example Corp. -Clementina Robel is a manager. -Hattie Schroeder joins us as an manager on the Example project. -Help me welcome our newest engineer, Pauline Eichmann. -Isabella Sauer is a engineer with Example Corp. -Jaclyn Gaylord is a engineer with Example Corp. -Announcing engineer Nyah Stiedemann. -Our latest new employee, Keith Kris PhD, has been a manager in the industry for 4 years. -Antwon Wyman is a manager with Example Corp. -Mrs. Kelsie Champlin is retiring as a manager. -Our latest new employee, Tamia Grady, has been a manager in the industry for 4 years. -Lue Russel has been a engineer for 14 years. -Help me welcome our newest engineer, Kenyon Reinger. -Akeem O'Hara has been a engineer for 14 years. -Our latest new employee, Mrs. Thomas Funk, has been a manager in the industry for 4 years. -Bryce Hyatt MD has been a engineer for 14 years. -Rebeca Christiansen has been a manager for 14 years. -Alene Russel has been an manager for over a decade. -Mikel Upton joins us as an engineer on the Example project. -Announcing manager Ms. Demond Walker. -Taya Leuschke MD has been an engineer for over a decade. -Dr. Deon Huel joins us as an manager on the Example project. -Josie Simonis Sr., an manager, will be presenting the award. -Announcing engineer Dr. Leonie Cummerata. -Our latest new employee, Jamar Bins II, has been a manager in the industry for 4 years. -Samara Ledner has been a manager for 14 years. -Our latest new employee, Coy Weber III, has been a engineer in the industry for 4 years. -Announcing engineer Miss Kyler Grant. -Maya Little is a engineer. -Help me welcome our newest engineer, Brycen Walsh PhD. -Regan Parker is a engineer. -Our latest new employee, Mrs. Jayson Kohler, has been a engineer in the industry for 4 years. -Erika Balistreri IV will be the new manager for the team. -Lavinia Rolfson is a engineer with Example Corp. -Help me welcome our newest engineer, Seamus Wintheiser. -Floy Reilly is a manager with Example Corp. -Dr. Omer Leannon has been an manager for over a decade. -Our latest new employee, Paxton Hansen, has been a engineer in the industry for 4 years. -Announcing manager Darlene Volkman. -Lamar Hermiston has been an engineer for over a decade. -Announcing engineer Johnny Steuber MD. -Amanda Harris is a engineer in the high tech industry. -Help me welcome our newest manager, Meagan Herman MD. -Avis Stark IV has been an engineer for over a decade. -Fay Rodriguez DDS is a manager in the high tech industry. -Leif Quigley joins us as an engineer on the Example project. -Announcing manager Charlie Davis. -Jaylon Daugherty IV is a manager. -Tyreek Shields MD, an engineer, will be presenting the award. -Announcing engineer Cassandre Stracke. -Announcing engineer Tommie Wilkinson Jr.. -Anibal Gorczany is a engineer in the high tech industry. -Nova Grady III joins us as an manager on the Example project. -Our latest new employee, Dr. Rosemarie Olson, has been a manager in the industry for 4 years. -Amparo O'Reilly is a manager in the high tech industry. -Help me welcome our newest engineer, Keely Douglas. -Mr. Immanuel Collins has been an manager for over a decade. -Trisha McKenzie II will be the new engineer for the team. -Leola Considine is a manager in the high tech industry. -Bert Rodriguez has been a manager for 14 years. -Cassandre Schroeder will be the new engineer for the team. -Cory Hackett PhD has been an manager for over a decade. -Mr. Bertram Willms is a manager in the high tech industry. -Announcing engineer Peggie Jerde. -Iva Lueilwitz is a manager in the high tech industry. -Miss Arvilla Hermiston is a engineer in the high tech industry. -Rickey Schmidt is a manager. -Emery Denesik Jr. is a manager in the high tech industry. -Roel Bernhard has been a manager for 14 years. -Nathanael Bartell is a manager with Example Corp. -Johann Nikolaus, an manager, will be presenting the award. -Help me welcome our newest engineer, Pasquale Wuckert Sr.. -Dr. Itzel Hane will be the new engineer for the team. -Carol Rosenbaum will be the new engineer for the team. -Marjory Feil, an engineer, will be presenting the award. -Help me welcome our newest manager, Arvilla Prohaska. -Lester Huel is a engineer. -Chad Pacocha is a engineer in the high tech industry. -Announcing engineer Joseph Brakus. -Aaron Turcotte is retiring as a manager. -Mrs. Madaline Doyle will be the new engineer for the team. -Brennan Bogan has been a manager for 14 years. -Ms. Minerva Homenick, an manager, will be presenting the award. -Amie Turner has been a engineer for 14 years. -Sheila Romaguera is a manager with Example Corp. -Our latest new employee, Lazaro Fritsch, has been a manager in the industry for 4 years. -Our latest new employee, Dr. Myrtice Kuhic, has been a engineer in the industry for 4 years. -Walter Gusikowski is a engineer in the high tech industry. -Carmel Lehner has been a engineer for 14 years. -Announcing manager Roxanne Smitham. -Emilia Heidenreich DDS has been an manager for over a decade. -Cyrus Willms III has been a engineer for 14 years. -Help me welcome our newest engineer, Amos Wilderman. -Help me welcome our newest engineer, Bettie Will. -Announcing engineer Daniella Dickinson. -Judson Gutkowski II, an engineer, will be presenting the award. -Otho Greenholt, an engineer, will be presenting the award. -Ari Wuckert PhD will be the new engineer for the team. -Help me welcome our newest engineer, Reese Aufderhar. -Jovani Nolan III is a engineer with Example Corp. -Winona Willms Sr. is a manager with Example Corp. -Ms. Tierra Wyman joins us as an manager on the Example project. -Melissa Treutel will be the new manager for the team. -Announcing manager Celestine Langworth. -Announcing manager Everardo Beier. -Therese Goldner MD is a manager with Example Corp. -Miss Cydney Kerluke has been an manager for over a decade. -Price Bernier is a manager. -Annetta Hane is a engineer in the high tech industry. -Darrell Mertz is retiring as a engineer. -Help me welcome our newest engineer, Lela Labadie PhD. -Our latest new employee, Olin Nikolaus, has been a manager in the industry for 4 years. -Dr. Caterina Runolfsson has been a engineer for 14 years. -Announcing engineer Matt Beier. -William Hessel has been an engineer for over a decade. -Claudia Cole has been an manager for over a decade. -Mr. Chet Smith is a manager in the high tech industry. -Dayne Greenholt has been a manager for 14 years. -Announcing engineer Serenity Carroll IV. -Our latest new employee, Emile Dooley, has been a manager in the industry for 4 years. -Help me welcome our newest manager, Bonnie Heathcote. -Sheila Brown joins us as an engineer on the Example project. -Alisha Greenholt is a manager in the high tech industry. -Cindy Wintheiser Sr. is a engineer with Example Corp. -Otis Mueller joins us as an manager on the Example project. -Gerson Hyatt joins us as an manager on the Example project. -Mrs. Jovan Aufderhar has been an manager for over a decade. -Alexys Torp is a manager in the high tech industry. -Lucius Stanton, an manager, will be presenting the award. -Our latest new employee, Colton DuBuque, has been a engineer in the industry for 4 years. -Ms. Annamarie Medhurst is a manager. -Bryana Grady III will be the new manager for the team. -Ms. Wayne Mosciski, an engineer, will be presenting the award. -Julio Kuphal IV will be the new manager for the team. -Alize Kshlerin has been an manager for over a decade. -Announcing engineer Mrs. Alexandria Harvey. -Aileen Kerluke joins us as an engineer on the Example project. -Bria Howell is a manager. -Aida Stanton IV is a engineer. -Greta Hoppe will be the new manager for the team. -Announcing engineer Garrett VonRueden. -Tressa Sipes has been an manager for over a decade. -Gay Kutch has been an engineer for over a decade. -Rachelle Marks has been an manager for over a decade. -Tyreek Weber will be the new manager for the team. -Donnell Sauer I has been an manager for over a decade. -Help me welcome our newest manager, Mr. Ed Russel. -Darien Wolf, an engineer, will be presenting the award. -Sanford Marks has been an manager for over a decade. -Erica Cole is a engineer in the high tech industry. -Fanny Shields is a manager in the high tech industry. -Mortimer Nicolas joins us as an manager on the Example project. -Reid Daniel is a engineer with Example Corp. -Joseph Bechtelar joins us as an engineer on the Example project. -Our latest new employee, Matilde Corkery, has been a manager in the industry for 4 years. -Luisa White has been a engineer for 14 years. -Irving Hettinger has been an manager for over a decade. -Molly Ziemann, an engineer, will be presenting the award. -Garrett Renner, an engineer, will be presenting the award. -Cathryn Conroy has been an engineer for over a decade. -Crystel Reichert PhD will be the new manager for the team. -Help me welcome our newest engineer, Reese Mraz. -Fiona Lakin is a manager with Example Corp. -Miss Finn Koelpin, an engineer, will be presenting the award. -Our latest new employee, Genevieve Murray V, has been a manager in the industry for 4 years. -Newell Leffler is a manager in the high tech industry. -Our latest new employee, Isabel Ankunding, has been a manager in the industry for 4 years. -Announcing manager Malcolm Dickinson. -Celestine Greenfelder is a manager with Example Corp. -Dixie Pouros has been a manager for 14 years. -Mrs. Hipolito Senger is a manager with Example Corp. -Help me welcome our newest manager, Narciso Kulas. -Announcing engineer Lera Leannon. -Reyna Douglas has been an manager for over a decade. -Ari Rau, an manager, will be presenting the award. -Maximo Murphy is a manager. -Monserrate Ankunding is retiring as a engineer. -Vaughn Emmerich V has been an engineer for over a decade. -Announcing engineer Dena Wiza. -Ara Boyer has been an engineer for over a decade. -Our latest new employee, Leanna Bauch, has been a manager in the industry for 4 years. -Our latest new employee, Mr. Marcella Daugherty, has been a engineer in the industry for 4 years. -Our latest new employee, Ms. Billie Smith, has been a manager in the industry for 4 years. -Humberto Maggio joins us as an manager on the Example project. -Jarrell Roberts has been a manager for 14 years. -Ronaldo Hauck is a manager with Example Corp. -Delbert Corwin joins us as an engineer on the Example project. -Mrs. Stewart Hammes is a engineer with Example Corp. -Rudolph Deckow DDS is retiring as a engineer. -Our latest new employee, Carmine Fritsch, has been a manager in the industry for 4 years. -Hannah Mayer DVM, an engineer, will be presenting the award. -Merle Boyle has been a manager for 14 years. -Help me welcome our newest manager, Price Hand. -Miss Rhea Mitchell has been an engineer for over a decade. -Help me welcome our newest manager, Clarissa Pacocha. -Help me welcome our newest manager, Mrs. Loma Witting. -Announcing engineer Johnny Dickens DDS. -Miss Althea Carter is a engineer in the high tech industry. -Elizabeth Trantow joins us as an manager on the Example project. -Everett Lind is retiring as a manager. -Esta Smith I, an engineer, will be presenting the award. -Clemmie Quitzon is a engineer with Example Corp. -Lilian Mitchell is a engineer. -Mac Schulist I is a engineer. -Brooke Ward joins us as an manager on the Example project. -Elaina Beier is a manager in the high tech industry. -Kaleb Larson joins us as an engineer on the Example project. -Caden Sporer will be the new engineer for the team. -Our latest new employee, Candice Runte, has been a manager in the industry for 4 years. -Our latest new employee, Leonie Pollich, has been a manager in the industry for 4 years. -Mrs. Vivian Ebert, an manager, will be presenting the award. -Our latest new employee, Ewell Wolf, has been a manager in the industry for 4 years. -Our latest new employee, Monty Davis, has been a manager in the industry for 4 years. -Jeremie Towne joins us as an manager on the Example project. -Estefania Ziemann has been an manager for over a decade. -Help me welcome our newest manager, Mr. Lon Daniel. -Help me welcome our newest engineer, Meagan Durgan. -Jules Beahan is a engineer. -Mr. Hassan Hegmann is a engineer in the high tech industry. -Help me welcome our newest manager, Albina Morissette. -Saige Cummings has been a engineer for 14 years. -Corbin Rempel is retiring as a manager. -Mr. Dario Pfeffer, an manager, will be presenting the award. -Mrs. Rodrigo Grady will be the new engineer for the team. -Delmer Parker is a manager with Example Corp. -Help me welcome our newest manager, Camryn Rath. -Kirsten Koss joins us as an manager on the Example project. -Kaylie Hayes is a engineer in the high tech industry. -Cornell Ebert is a manager with Example Corp. -Reva Huels will be the new manager for the team. -Moses Raynor, an engineer, will be presenting the award. -Willard Gottlieb will be the new engineer for the team. -Alexys Kerluke is retiring as a engineer. -Desiree Streich has been an manager for over a decade. -Ms. Gayle Prohaska is a engineer. -Freddie Mayer is a engineer. -Corrine Larson will be the new manager for the team. -Emilio Jast, an engineer, will be presenting the award. -Mrs. Bobby Bernier is retiring as a manager. -Johnson Beahan will be the new manager for the team. -Jewel Jacobi PhD is a manager in the high tech industry. -Dr. Lori Hagenes is a engineer with Example Corp. -Dr. Austyn Streich has been an manager for over a decade. -Help me welcome our newest manager, Alex Greenholt. -Ida Lang has been an engineer for over a decade. -Our latest new employee, Uriel Powlowski, has been a manager in the industry for 4 years. -Announcing manager Domenic Ratke. -Herminio Schamberger MD is a engineer with Example Corp. -Alexa Shanahan Sr. is a engineer in the high tech industry. -Announcing manager Kasandra Brekke. -Maida Marquardt will be the new manager for the team. -Our latest new employee, Florian McGlynn, has been a engineer in the industry for 4 years. -Hilton Jast, an engineer, will be presenting the award. -Miss Maryse Sporer joins us as an engineer on the Example project. -Zoie Gulgowski has been an engineer for over a decade. -Help me welcome our newest engineer, Minnie Bernier. -Help me welcome our newest engineer, Mabelle Pouros Jr.. -Miss Axel Dach will be the new manager for the team. -Announcing engineer Golda Pfannerstill PhD. -Tristin Goldner has been a manager for 14 years. -Dr. Jaunita Schinner, an manager, will be presenting the award. -Miss Leonel Shields has been an manager for over a decade. -Mrs. Kirstin Daugherty joins us as an manager on the Example project. -Our latest new employee, Aleen Quigley MD, has been a manager in the industry for 4 years. -Krystina Beatty PhD, an engineer, will be presenting the award. -Our latest new employee, Alanis Keebler, has been a manager in the industry for 4 years. -Help me welcome our newest engineer, Monica Botsford. -Roselyn Deckow is a manager with Example Corp. -Help me welcome our newest manager, Mrs. Lucy McGlynn. -Rey Gibson has been a engineer for 14 years. -Shaylee Jerde is a engineer with Example Corp. -Ethyl Fay is a manager. -Help me welcome our newest manager, Dulce Fay. -Dayana Goyette will be the new engineer for the team. -Kurt Lesch Sr. has been an engineer for over a decade. -Alejandra Senger is a manager with Example Corp. -Our latest new employee, Broderick Jakubowski, has been a engineer in the industry for 4 years. -Help me welcome our newest engineer, Neva Hartmann. -Tamia Ziemann has been an engineer for over a decade. -Maida Hermiston DVM is a manager in the high tech industry. -Paige Rempel joins us as an engineer on the Example project. -Our latest new employee, Sherman Torphy, has been a manager in the industry for 4 years. -Doug Pfannerstill joins us as an manager on the Example project. -Our latest new employee, Mr. Alicia Rice, has been a engineer in the industry for 4 years. -Our latest new employee, Justine Sawayn, has been a manager in the industry for 4 years. -Camren Kerluke has been a engineer for 14 years. -Kitty Trantow is a manager in the high tech industry. -Baby Barrows joins us as an manager on the Example project. -Bridie Haley has been an manager for over a decade. -Kyra Kemmer is a engineer. -Khalil Blanda joins us as an manager on the Example project. -Madalyn Kassulke is a manager with Example Corp. -Ebba Gislason I joins us as an engineer on the Example project. -Our latest new employee, Gloria Skiles, has been a manager in the industry for 4 years. -Announcing manager Gardner Conroy. -Wilfrid Gottlieb joins us as an engineer on the Example project. -Roslyn Labadie V joins us as an engineer on the Example project. -Eliseo Considine DDS is a engineer with Example Corp. -Announcing engineer Lowell Keebler. -Hailee Kuhn is retiring as a manager. -Cordia Bayer has been a manager for 14 years. -Harry Koch has been an manager for over a decade. -Sarai Rodriguez PhD is a manager with Example Corp. -Mrs. Elnora Kutch has been an manager for over a decade. -Ms. Geovany Toy is a manager. -Zoe Jacobs is a manager with Example Corp. -Help me welcome our newest engineer, D'angelo Ratke. -Kenna Purdy, an manager, will be presenting the award. -Help me welcome our newest manager, Cale Prohaska. -Shania Conn has been a engineer for 14 years. -Miss Daphnee Schuster is a engineer with Example Corp. -Buford Koelpin is retiring as a manager. -Help me welcome our newest manager, Erick Jaskolski. -Briana Kunze MD is retiring as a engineer. -Rahsaan Hodkiewicz is a engineer. -Help me welcome our newest engineer, Dr. Izabella Nolan. -Announcing engineer Ms. Lavinia Wuckert. -Ms. Theron Grimes is retiring as a engineer. -Christina Mertz is a engineer. -Mr. Toby Powlowski is a manager with Example Corp. -Ms. Yolanda Spinka is a manager in the high tech industry. -Announcing manager Ms. Dayne McCullough. -Patsy Legros has been an manager for over a decade. -Carley Berge PhD is retiring as a manager. -Jairo Champlin is a manager. -Elvie Schiller is a engineer with Example Corp. -Delta Bayer V joins us as an manager on the Example project. -Dr. Russ Treutel will be the new engineer for the team. -Help me welcome our newest engineer, Gerry Bartell. -Lennie Trantow will be the new manager for the team. -Our latest new employee, Orland Streich, has been a engineer in the industry for 4 years. -Alberta Considine, an manager, will be presenting the award. -Our latest new employee, Ms. Leda Rowe, has been a engineer in the industry for 4 years. -Mrs. Gregory Hettinger is a engineer with Example Corp. -Elinore Grant joins us as an manager on the Example project. -Buddy Nader is a engineer. -Our latest new employee, Violette Stanton Sr., has been a manager in the industry for 4 years. -Hilton Daniel will be the new engineer for the team. -Ms. Julia Eichmann has been a engineer for 14 years. -Earline McCullough has been a manager for 14 years. -Juston Turner III is a engineer. -Vivien Volkman has been an engineer for over a decade. -Mina Streich MD is a engineer in the high tech industry. -Timmothy Lowe II joins us as an manager on the Example project. -Dashawn Bechtelar I joins us as an manager on the Example project. -Viola Parisian has been an manager for over a decade. -Johnson Grady is a manager. -Help me welcome our newest manager, Marcos Huel. -Announcing engineer Donna Crona. -Bella Gibson has been an engineer for over a decade. -Broderick Dickens is a manager in the high tech industry. -Announcing manager Bobbie Heathcote. -Lauretta McClure DDS will be the new engineer for the team. -Dortha Grimes Jr. has been a engineer for 14 years. -Annamae Gislason is a manager in the high tech industry. -Carli Friesen is a engineer in the high tech industry. -Ms. Rupert Waters is a manager with Example Corp. -Americo Walsh DDS has been a manager for 14 years. -Melyssa Pacocha is a engineer. -Mr. Deshaun Swift, an manager, will be presenting the award. -Frank Wolf is a manager in the high tech industry. -Claudine Bruen joins us as an engineer on the Example project. -Our latest new employee, Jett Zemlak, has been a engineer in the industry for 4 years. -Ms. Ruth Weber has been an engineer for over a decade. -Adella Hammes will be the new manager for the team. -Help me welcome our newest manager, Harmon Conn. -Announcing engineer Jude Durgan. -Help me welcome our newest engineer, Frank Veum I. -Keon Berge, an engineer, will be presenting the award. -Announcing engineer Ashleigh McGlynn. -Peyton Heidenreich, an manager, will be presenting the award. -Mr. Filomena Satterfield, an engineer, will be presenting the award. -Jeffrey Hessel joins us as an engineer on the Example project. -Mr. Libbie Denesik is a manager. -Announcing engineer Cole Leffler I. -Haskell Greenfelder IV is a manager in the high tech industry. -Carmela Koss has been an engineer for over a decade. -Omer Larson is a engineer in the high tech industry. -Earlene Smith is a engineer. -Lisandro Howell II is a manager with Example Corp. -Announcing manager Nash Lakin. -Help me welcome our newest engineer, Torrance Bode. -Gene Emmerich is a manager. -Jayden Lueilwitz is retiring as a engineer. -Mable Satterfield is a engineer in the high tech industry. -Braulio Hodkiewicz will be the new manager for the team. -Scotty Reilly has been an manager for over a decade. -Diana Kautzer Sr. is a manager. -Cyrus Keeling is retiring as a engineer. -Announcing engineer Mrs. Alexie Upton. -Announcing manager Jamarcus Kihn. -Hudson Stokes will be the new manager for the team. -Our latest new employee, Danyka Gulgowski, has been a engineer in the industry for 4 years. -Kiara Rohan is a manager. -Mr. Asia Price is retiring as a engineer. -Danial Walsh Jr. has been a engineer for 14 years. -Announcing manager Mr. Mariana Boyer. -Matilde Bechtelar has been an manager for over a decade. -Help me welcome our newest manager, Sammy Thompson. -Chris Leannon joins us as an engineer on the Example project. -Verda Hahn is retiring as a manager. -Jarod McGlynn, an manager, will be presenting the award. -Hattie Halvorson, an manager, will be presenting the award. -Myron Aufderhar will be the new manager for the team. -Merlin Koelpin is a manager. -Mr. Cade Farrell will be the new manager for the team. -Allan Collier is a engineer with Example Corp. -Mrs. Stella Labadie is a engineer with Example Corp. -Ettie Zulauf is a manager in the high tech industry. -Annabel Towne has been an engineer for over a decade. -Megane DuBuque III has been a manager for 14 years. -Jerrold Gorczany is a manager in the high tech industry. -Helena Flatley has been a manager for 14 years. -Our latest new employee, Norma Kautzer II, has been a engineer in the industry for 4 years. -Thurman Powlowski has been an manager for over a decade. -Dee Herzog has been an engineer for over a decade. -Help me welcome our newest engineer, Olaf Strosin. -Mrs. Imogene Kunde is retiring as a engineer. -Miss Merl Hauck is retiring as a manager. -Announcing manager Miss Haskell Halvorson. -Mr. Margaret Johns will be the new engineer for the team. -Katelyn Huels will be the new manager for the team. -Else Swaniawski will be the new manager for the team. -Gwen Kessler is a manager in the high tech industry. -Our latest new employee, Santino Raynor, has been a engineer in the industry for 4 years. -Our latest new employee, Mrs. Alene Zieme, has been a manager in the industry for 4 years. -Our latest new employee, Amira Hermiston, has been a manager in the industry for 4 years. -Our latest new employee, Hilbert Lindgren, has been a manager in the industry for 4 years. -Nayeli Rolfson has been a manager for 14 years. -Rosalee Kovacek joins us as an engineer on the Example project. -Ashton Weissnat joins us as an engineer on the Example project. -Sonya Skiles is a manager in the high tech industry. -Announcing manager Mr. Clarabelle Heaney. -Polly McClure I, an engineer, will be presenting the award. -Announcing manager Joaquin O'Conner. -Lionel Moen, an manager, will be presenting the award. -Lavina Farrell PhD has been an manager for over a decade. -Mr. Marcel Jacobs has been a engineer for 14 years. -Our latest new employee, Freeda Beer, has been a manager in the industry for 4 years. -Announcing engineer Karley Considine Sr.. -Mrs. Benedict Hills will be the new engineer for the team. -Maverick Kris is retiring as a engineer. -Jana McDermott is retiring as a engineer. -Lewis Quitzon is a manager in the high tech industry. -Jazlyn Littel will be the new manager for the team. -Serena Pfannerstill is a manager with Example Corp. -Dr. Christiana Rodriguez is retiring as a engineer. -Crystal Hintz has been a engineer for 14 years. -Antonette Kiehn is a manager in the high tech industry. -Our latest new employee, Vena Osinski PhD, has been a engineer in the industry for 4 years. -Herta Gorczany has been a engineer for 14 years. -Carol Quigley is a manager in the high tech industry. -Blanche Gottlieb is retiring as a manager. -Ms. Kellie Ledner is a engineer with Example Corp. -Rae Daniel has been an manager for over a decade. -Elizabeth Borer has been an engineer for over a decade. -Our latest new employee, Nelle Botsford, has been a engineer in the industry for 4 years. -Our latest new employee, Jimmie Roob PhD, has been a engineer in the industry for 4 years. -Delia Erdman V has been a manager for 14 years. -Our latest new employee, Cyril Brekke, has been a manager in the industry for 4 years. -Miss Alphonso Bailey, an manager, will be presenting the award. -Our latest new employee, Suzanne Treutel, has been a engineer in the industry for 4 years. -Alayna Krajcik joins us as an engineer on the Example project. -Roberta Lind is a manager with Example Corp. -Ryleigh Dicki, an manager, will be presenting the award. -Mrs. Adolph Carroll, an engineer, will be presenting the award. -Elena Emard has been a manager for 14 years. -Meggie Bechtelar will be the new engineer for the team. -Jan Rolfson will be the new engineer for the team. -Our latest new employee, Greg Roberts, has been a engineer in the industry for 4 years. -Alison Labadie has been a manager for 14 years. -Cielo Dietrich joins us as an manager on the Example project. -Erika Dibbert has been a engineer for 14 years. -Michale Bailey Sr. has been an manager for over a decade. -Rene Bahringer is a manager with Example Corp. -Announcing engineer Joyce Hyatt DVM. -Turner Windler is a engineer with Example Corp. -Camille Ullrich joins us as an engineer on the Example project. -Announcing manager Faye Kemmer. -Albertha Crona is retiring as a manager. -Miss Vicente Schowalter is retiring as a engineer. -Bridgette Fay will be the new engineer for the team. -Javier Mayer has been an manager for over a decade. -America O'Keefe is a manager in the high tech industry. -Reese Swift is retiring as a manager. -Announcing engineer Miss Susan Tromp. -Cathy Berge, an engineer, will be presenting the award. -Freddie Kuhic has been an engineer for over a decade. -Announcing engineer Vivian Wolff. -Rosalee Nienow, an engineer, will be presenting the award. -Marcelina Wyman joins us as an manager on the Example project. -Carmen Jakubowski joins us as an engineer on the Example project. -Patience Kuhn is a engineer with Example Corp. -Ross O'Reilly has been a manager for 14 years. -Our latest new employee, Carol Schmitt, has been a engineer in the industry for 4 years. -Ms. Gracie Durgan is a engineer with Example Corp. -Announcing engineer Annabelle McGlynn. -Mrs. Saul Carter has been an manager for over a decade. -Kayla Leuschke has been a manager for 14 years. -Our latest new employee, Okey Wilderman PhD, has been a manager in the industry for 4 years. -Rodrigo Kovacek is a manager in the high tech industry. -Ms. Arnoldo Lehner is a engineer with Example Corp. -Ms. Alek Rippin is a manager in the high tech industry. -Keara Kemmer III joins us as an engineer on the Example project. -Garry Mertz is a manager in the high tech industry. -Our latest new employee, Aliyah Jacobson, has been a manager in the industry for 4 years. -Mrs. Lauriane Reynolds joins us as an manager on the Example project. -Dallin Cormier is retiring as a manager. -Dr. Ashlee Stehr is a engineer. -Miss Kendra Tremblay is retiring as a manager. -Clement Cremin is a engineer. -Nicholas Lakin is retiring as a manager. -Announcing engineer Jessyca Bosco. -Richard Emmerich is retiring as a manager. -Mrs. Norwood Rempel, an manager, will be presenting the award. -Maritza Osinski has been an manager for over a decade. -Lorena Barton is a manager in the high tech industry. -Mr. Keara Marvin will be the new engineer for the team. -Announcing manager Jeanie Hirthe. -Help me welcome our newest manager, Cole Abshire. -Gudrun Lind, an manager, will be presenting the award. -Skylar Goldner Jr. will be the new manager for the team. -Cicero Terry has been an manager for over a decade. -Paul O'Keefe DVM will be the new engineer for the team. -Ferne Schoen joins us as an engineer on the Example project. -Announcing manager Keaton Kling. -Announcing engineer Alicia Bauch. -Jalyn Huels joins us as an manager on the Example project. -Wilburn Weissnat is a engineer. -Simeon Fisher is a engineer with Example Corp. -Patrick Pouros joins us as an engineer on the Example project. -Garrison Roob has been a engineer for 14 years. -Mariah Mitchell I has been an manager for over a decade. -Guy Hintz Jr. is a manager in the high tech industry. -Earl Sipes has been an engineer for over a decade. -Miss Thalia Ratke is a engineer. -Dr. Leanne Greenfelder is a engineer. -Blanca Kulas has been an manager for over a decade. -Fanny Wisozk II is retiring as a engineer. -Help me welcome our newest engineer, Flo Ratke. -Mr. Herminio O'Kon joins us as an engineer on the Example project. -Help me welcome our newest engineer, Emerald Stamm. -Dawn Grimes is a manager in the high tech industry. -Help me welcome our newest engineer, Kathryne Hills. -Jordane Thiel is retiring as a manager. -Antonio Hammes I has been an manager for over a decade. -Zackery McDermott has been an manager for over a decade. -Our latest new employee, Shania Schultz PhD, has been a engineer in the industry for 4 years. -Genevieve Moore is a engineer. -Mattie Turner has been a manager for 14 years. -Caesar Glover I has been a manager for 14 years. -Mitchell Haley Jr. has been a engineer for 14 years. -Itzel Wisozk is retiring as a engineer. -Announcing engineer Mr. Victoria Mraz. -Announcing engineer Verona Bailey. -Lorenza Beahan is a engineer in the high tech industry. -Tre DuBuque will be the new manager for the team. -Missouri Walker is retiring as a manager. -Reynold Smitham joins us as an manager on the Example project. -Jedediah Bins Jr. joins us as an manager on the Example project. -Miss Jacinthe Tillman is a manager in the high tech industry. -Mrs. Imelda Okuneva is a manager in the high tech industry. -Chadd Botsford joins us as an manager on the Example project. -Jailyn Sanford has been an manager for over a decade. -Monte Windler is a engineer. -Our latest new employee, Marguerite Kozey, has been a engineer in the industry for 4 years. -Dr. Buck Kuhic joins us as an manager on the Example project. -Cristian Monahan has been an manager for over a decade. -Help me welcome our newest manager, Madge Rosenbaum V. -Serena Kunze is a engineer. -Max Mueller joins us as an manager on the Example project. -Miss Eda Torp, an manager, will be presenting the award. -Mrs. Alivia Harris is a engineer with Example Corp. -Dr. Haylie Waters will be the new engineer for the team. -Our latest new employee, Maxie Simonis IV, has been a engineer in the industry for 4 years. -Dennis Olson DDS will be the new engineer for the team. -Jason Lesch is retiring as a engineer. -Marlin Hermiston, an manager, will be presenting the award. -Devyn Gaylord Sr., an manager, will be presenting the award. -Jalon Schaden, an manager, will be presenting the award. -Dillan Mertz PhD is retiring as a engineer. -Nico Bosco joins us as an manager on the Example project. -Help me welcome our newest engineer, Abigale Stark. -Ms. Rubye Hahn will be the new engineer for the team. -Zoey Lemke II is a engineer in the high tech industry. -Norris Ziemann is a manager in the high tech industry. -Anissa Wintheiser joins us as an manager on the Example project. -Janie Halvorson has been an manager for over a decade. -Geovany Dickinson I, an engineer, will be presenting the award. -Rosalind Lebsack has been a engineer for 14 years. -Ms. Rosalinda Brekke is a engineer. -Estefania Lowe is a engineer in the high tech industry. -Eda Lind joins us as an engineer on the Example project. -Help me welcome our newest manager, Lessie Langworth. -Virginie Turcotte has been an manager for over a decade. -Maud Terry is a manager. -Announcing manager Bobbie Cartwright. -Keaton Crona joins us as an engineer on the Example project. -Norris Schumm, an manager, will be presenting the award. -Miss Jamey Gusikowski joins us as an engineer on the Example project. -Gus Heathcote, an engineer, will be presenting the award. -Julio Ward is a engineer. -Samara Goldner is a engineer in the high tech industry. -Reuben Mohr, an engineer, will be presenting the award. -Announcing manager Clinton Howell. -Keyshawn Sipes will be the new engineer for the team. -Giovani Marks will be the new manager for the team. -Norberto Romaguera will be the new manager for the team. -Rose Mante is retiring as a manager. -Nathan Stoltenberg is a manager in the high tech industry. -Keira Breitenberg is a engineer with Example Corp. -Tanner Borer has been a manager for 14 years. -Ms. Ike Pfeffer is a engineer with Example Corp. -Dr. Helena Ernser will be the new manager for the team. -Sophie Harris is a engineer with Example Corp. -Announcing manager Mrs. Jessyca Bayer. -Aleen Bogisich is a manager with Example Corp. -Zakary Schmeler, an manager, will be presenting the award. -Our latest new employee, Katelin Luettgen, has been a manager in the industry for 4 years. -America Schuppe V is a manager in the high tech industry. -Adalberto VonRueden is a engineer. -Our latest new employee, Mohamed Lueilwitz, has been a manager in the industry for 4 years. -Luna Lowe will be the new manager for the team. -Our latest new employee, Bradley Marquardt DVM, has been a engineer in the industry for 4 years. -Jamie Goldner will be the new manager for the team. -Haley Bradtke has been an engineer for over a decade. -Help me welcome our newest manager, Paris Langworth. -Trevor Lubowitz MD has been an manager for over a decade. -Fred Glover has been an engineer for over a decade. -Jimmy Jakubowski I is retiring as a engineer. -Our latest new employee, Asia Runolfsson, has been a manager in the industry for 4 years. -Destiny Cassin has been a manager for 14 years. -Barry Reilly is a manager. -Amari Mills PhD is a engineer in the high tech industry. -Sandrine Nitzsche joins us as an engineer on the Example project. -Help me welcome our newest engineer, Rocio Bechtelar DDS. -Ms. Charity MacGyver joins us as an manager on the Example project. -Mrs. Mac Robel is a manager in the high tech industry. -Icie Cummerata is a manager in the high tech industry. -Our latest new employee, Felix Trantow, has been a manager in the industry for 4 years. -Dr. Makenzie Wuckert joins us as an manager on the Example project. -Melany Donnelly has been a manager for 14 years. -Tyrell Zulauf has been an engineer for over a decade. -Boyd Stroman is a engineer with Example Corp. -Jeramie Botsford is retiring as a manager. -Pascale Stehr is a engineer. -Savanna Wisoky is a engineer with Example Corp. -Justus Morar Jr. is a engineer. -Reymundo Thiel joins us as an manager on the Example project. -Dr. Geraldine Bogan has been a engineer for 14 years. -Ibrahim Treutel V is retiring as a engineer. -Alexandrine Rohan will be the new engineer for the team. -Emilio Witting is retiring as a manager. -Brook Lueilwitz will be the new manager for the team. -Mr. Madelyn Collins has been a engineer for 14 years. -Trenton Skiles, an engineer, will be presenting the award. -Demario Cremin joins us as an manager on the Example project. -Miss Leonel Ullrich is a engineer with Example Corp. -Our latest new employee, Nasir Predovic, has been a manager in the industry for 4 years. -Darion Zboncak I joins us as an manager on the Example project. -Ophelia Spencer is a engineer. -Deron Marks joins us as an manager on the Example project. -Our latest new employee, Mr. Warren Lemke, has been a engineer in the industry for 4 years. -Stephen O'Reilly has been an manager for over a decade. -Monica Orn has been a manager for 14 years. -Our latest new employee, Anahi Graham, has been a engineer in the industry for 4 years. -Ocie Carter joins us as an manager on the Example project. -Kailey Haag will be the new manager for the team. -Rupert Mills has been an engineer for over a decade. -Help me welcome our newest manager, Tina Braun. -Kiel Johns will be the new manager for the team. -Frederick DuBuque will be the new engineer for the team. -Our latest new employee, Grace Boyle PhD, has been a manager in the industry for 4 years. -Emily Wyman has been a engineer for 14 years. -Branson Hoeger is retiring as a manager. -Miss Gussie Johnson has been a manager for 14 years. -Valerie Howell is a engineer. -Announcing manager Angie Hand. -Joesph O'Kon is a engineer with Example Corp. -Dr. Leatha Greenfelder will be the new engineer for the team. -Gregoria Lindgren is a manager with Example Corp. -Help me welcome our newest engineer, Rollin Lindgren PhD. -Help me welcome our newest manager, Randy Ankunding. -Jesse Bosco is retiring as a engineer. -Mrs. Amari Schimmel, an manager, will be presenting the award. -Haven O'Reilly DVM is retiring as a engineer. -Bradford Swaniawski has been an engineer for over a decade. -Ms. Jazlyn Runolfsdottir is a manager with Example Corp. -Enoch Ratke is a manager in the high tech industry. -Madaline Sporer has been an engineer for over a decade. -Nathen Turcotte will be the new manager for the team. -Our latest new employee, Sheridan Homenick, has been a manager in the industry for 4 years. -Dr. Rosalinda Breitenberg, an manager, will be presenting the award. -Help me welcome our newest manager, Kali O'Conner PhD. -Jaleel Lockman joins us as an manager on the Example project. -Miss Demarcus Erdman is a manager. -Destiny Miller is a engineer in the high tech industry. -Krystina Funk has been an manager for over a decade. -Ruth Hirthe has been a manager for 14 years. -Mohammed Boehm has been a engineer for 14 years. -Michale Farrell Sr. has been an engineer for over a decade. -Our latest new employee, Kaya Hermann, has been a engineer in the industry for 4 years. -Our latest new employee, Mabelle Haley, has been a engineer in the industry for 4 years. -Ms. Bessie Moen is a engineer in the high tech industry. -Announcing manager Vito Smitham. -Carley Rogahn Sr. is retiring as a engineer. -Carli West IV is retiring as a engineer. -Alexie Raynor has been an engineer for over a decade. -Beth Walsh has been a engineer for 14 years. -Shanna Witting joins us as an manager on the Example project. -Evelyn Zemlak joins us as an engineer on the Example project. -Sibyl O'Connell has been a engineer for 14 years. -Emmanuel Johnson has been a manager for 14 years. -Sandra Will has been a engineer for 14 years. -Samanta Quitzon I is a manager in the high tech industry. -Jacky Rosenbaum is retiring as a engineer. -Terrell Rolfson IV has been an manager for over a decade. -Kathryn Pouros is a engineer with Example Corp. -Dameon Cummerata has been an manager for over a decade. -Mr. Tanner O'Kon is retiring as a engineer. -Our latest new employee, Alex Anderson, has been a manager in the industry for 4 years. -Help me welcome our newest engineer, Junior Gottlieb. -Tianna Greenholt is a manager. -Kristy Nicolas, an manager, will be presenting the award. -Alanna Baumbach, an manager, will be presenting the award. -Christina Greenholt will be the new manager for the team. -Announcing engineer Cathrine Ferry. -Mr. Hayden Lueilwitz is a manager. -Our latest new employee, Rodrigo Stehr, has been a manager in the industry for 4 years. -Bobbie Stehr II is retiring as a engineer. -Carleton Kovacek Jr. is a engineer. -Jett Bruen joins us as an manager on the Example project. -Our latest new employee, Carroll Hegmann MD, has been a manager in the industry for 4 years. -Mrs. Newell Maggio, an manager, will be presenting the award. -Elyse Quigley has been a manager for 14 years. -Fabiola Wilkinson will be the new manager for the team. -Announcing manager Delilah Schulist. -Johnson Stehr is a manager with Example Corp. -Xavier Kilback DVM is a engineer with Example Corp. -Anastasia Wisozk is retiring as a engineer. -Announcing engineer Pascale Hamill. -Help me welcome our newest engineer, Mr. Rocky Quigley. -Lawrence McCullough is a engineer. -Magali Balistreri is a engineer in the high tech industry. -Help me welcome our newest manager, Annie Roob. -Announcing manager Amina Rempel. -Marc Gislason will be the new engineer for the team. -Norwood Boyle has been an manager for over a decade. -Monserrat Quitzon is a engineer in the high tech industry. -Our latest new employee, Mrs. Abbigail Reichel, has been a manager in the industry for 4 years. -Help me welcome our newest engineer, Isac Pacocha. -Unique Hansen is a engineer. -Help me welcome our newest manager, Ivy Dietrich DDS. -Gavin Kuhlman is retiring as a manager. -Judson Fahey PhD is a engineer in the high tech industry. -Orval Effertz joins us as an engineer on the Example project. -Karlie Keeling is a manager in the high tech industry. -Selina McDermott, an engineer, will be presenting the award. -Raquel Heller has been a manager for 14 years. -Blaise Ritchie has been an engineer for over a decade. -Lynn Mante is retiring as a engineer. -Constance Kemmer is a manager. -Miss Wilber Yundt has been an engineer for over a decade. -Dora Reinger joins us as an manager on the Example project. -Announcing engineer Mose Renner. -Our latest new employee, Koby Kirlin, has been a engineer in the industry for 4 years. -Denis Denesik has been a manager for 14 years. -Randal Runolfsson is a engineer in the high tech industry. -Help me welcome our newest engineer, Adrianna Bauch. -Zackery Adams MD is a engineer in the high tech industry. -Bruce Willms III has been a manager for 14 years. -Announcing engineer Ms. Alexander Jaskolski. -Miss Lera Schuster will be the new engineer for the team. -Hyman Herzog II is a engineer in the high tech industry. -Announcing manager Morton Labadie. -Allen Hane joins us as an manager on the Example project. -Katherine DuBuque Sr. will be the new manager for the team. -Lelah Bins is retiring as a engineer. -Ottis Connelly Jr. has been an manager for over a decade. -Ilene Ebert, an engineer, will be presenting the award. -Phoebe Mraz DDS is a manager with Example Corp. -Ms. Peter Osinski has been a manager for 14 years. -Dana Strosin, an manager, will be presenting the award. -Tiffany Reinger joins us as an engineer on the Example project. -Marlen Herman is a engineer. -Merlin Eichmann will be the new engineer for the team. -Mrs. Mateo Schaden has been an manager for over a decade. -Yasmeen Powlowski, an engineer, will be presenting the award. -Shaylee West is a manager with Example Corp. -Royal Batz PhD will be the new manager for the team. -Miss Vesta Stehr is a manager. -Edgardo Shields has been an engineer for over a decade. -Sylvan Kohler has been an engineer for over a decade. -Cordelia Pagac, an manager, will be presenting the award. -Dr. Stephany Kling joins us as an manager on the Example project. -Ms. Kelsi Wilkinson, an manager, will be presenting the award. -Shanna Ebert II has been an manager for over a decade. -Isobel Keebler has been a manager for 14 years. -Cynthia Osinski III is a engineer. -Lavonne Schinner is a manager with Example Corp. -Our latest new employee, Josue Bode, has been a engineer in the industry for 4 years. -Ms. Fermin Ward, an manager, will be presenting the award. -Nichole Casper, an engineer, will be presenting the award. -Cameron Kautzer has been an manager for over a decade. -Jacklyn Hayes MD is a engineer. -Dayne O'Conner MD has been an engineer for over a decade. -Miss Trystan Deckow is a engineer in the high tech industry. -Help me welcome our newest manager, Arvel Bode. -Mr. Jeffrey Nienow joins us as an manager on the Example project. -Dr. Hiram Dickinson is retiring as a manager. -Elsie Reinger is retiring as a manager. -Mustafa Hand is a engineer. -Schuyler McGlynn, an manager, will be presenting the award. -Shayne Tremblay is a manager in the high tech industry. -Levi Donnelly is a manager in the high tech industry. -Miss Queen Lind is a engineer with Example Corp. -Nadia Koch Jr. is a engineer in the high tech industry. -Jaclyn Kunze will be the new engineer for the team. -Cheyenne Johns is a engineer. -Margot Kutch is a engineer. -Help me welcome our newest engineer, Jimmie Friesen. -Tony O'Reilly is a manager in the high tech industry. -Antone Yundt is a engineer with Example Corp. -Iliana VonRueden DDS will be the new engineer for the team. -Elmo Howe DVM is retiring as a engineer. -Announcing engineer Marlon Muller. -Help me welcome our newest engineer, Aliyah Mitchell. -Jackson Carter is a engineer. -Brendan Bergnaum V, an manager, will be presenting the award. -Dallin Mueller will be the new engineer for the team. -Enid Hodkiewicz will be the new engineer for the team. -Edmund Cole is a manager with Example Corp. -Our latest new employee, Isabella Miller, has been a engineer in the industry for 4 years. -Maddison Raynor Sr. joins us as an engineer on the Example project. -Help me welcome our newest manager, Jasper Stoltenberg. -Herminio Rippin, an manager, will be presenting the award. -Mr. Laverne Hartmann is retiring as a manager. -Our latest new employee, Leonel Daugherty, has been a manager in the industry for 4 years. -Miss Gabrielle Gislason is a manager. -Help me welcome our newest engineer, Michele Quigley. -Jennie Greenfelder is a manager with Example Corp. -Announcing manager Dana Glover. -Keshaun Lakin is a engineer in the high tech industry. -Martin Collier has been an engineer for over a decade. -Help me welcome our newest engineer, Dewayne Nienow. -Miss Carlotta Gorczany has been a engineer for 14 years. -Rickie Jones will be the new engineer for the team. -Reanna Swift, an manager, will be presenting the award. -Maxie Johnston has been a manager for 14 years. -Isac Kuphal is retiring as a manager. -Miss Harvey Reichert is retiring as a engineer. -Isobel Hauck Jr. is a engineer in the high tech industry. -Leila Moen I joins us as an manager on the Example project. -Art Hirthe, an engineer, will be presenting the award. -Roxanne Ondricka is a manager with Example Corp. -Anais Kiehn will be the new engineer for the team. -Elvie Hodkiewicz has been an engineer for over a decade. -Kailyn Dietrich has been a engineer for 14 years. -Mr. Hershel Bernier has been an engineer for over a decade. -Paolo Goodwin DVM is a manager with Example Corp. -Geovany Lehner has been an engineer for over a decade. -Terry Raynor is a manager in the high tech industry. -Help me welcome our newest engineer, Audreanne Walker. -Breanne Ziemann is a manager in the high tech industry. -Brenden Altenwerth has been an engineer for over a decade. -Gilberto Paucek, an manager, will be presenting the award. -Wilson Jerde has been a engineer for 14 years. -Matilde Sporer III has been an engineer for over a decade. -Fredrick Thompson has been an engineer for over a decade. -Krista Emard is a engineer. -Frida Quitzon joins us as an engineer on the Example project. -Chanel Romaguera is a engineer with Example Corp. -Help me welcome our newest manager, Leon Larkin. -Tillman Thompson has been a engineer for 14 years. -Branson Stiedemann is retiring as a engineer. -Help me welcome our newest manager, Ophelia Wolff. -Jaeden Purdy will be the new manager for the team. -Daryl Sanford DDS is a engineer in the high tech industry. -Blair Hamill will be the new manager for the team. -Geo Gulgowski has been a engineer for 14 years. -Announcing engineer Mathew Von IV. -Kyleigh Hane is a engineer with Example Corp. -Announcing manager Ms. Reece Thompson. -Darryl Hagenes has been an engineer for over a decade. -Vivian Hudson is a engineer with Example Corp. -Our latest new employee, Norris Blanda PhD, has been a engineer in the industry for 4 years. -Help me welcome our newest engineer, Thomas Crist. -Mr. Bernadette Hackett is a manager in the high tech industry. -Grace Jacobi is a engineer. -Josefina Lynch is a engineer with Example Corp. -Fidel Hahn is retiring as a engineer. -Mrs. Declan Bechtelar will be the new manager for the team. -Yadira Deckow will be the new engineer for the team. -Neal Greenholt has been an engineer for over a decade. -Norwood Wehner is retiring as a engineer. -Miss Marisa Kirlin is a manager. -Mr. Clovis Steuber joins us as an engineer on the Example project. -Help me welcome our newest manager, Janiya Hartmann. -Delaney Walsh has been a engineer for 14 years. -Alycia Homenick has been a engineer for 14 years. -Saul Daugherty PhD is retiring as a manager. -Our latest new employee, Cecil Durgan, has been a engineer in the industry for 4 years. -Help me welcome our newest engineer, Cornell Pollich. -Seamus Hermann will be the new manager for the team. -Ms. Lempi Hickle is a engineer. -Mr. Willis Mertz has been a manager for 14 years. -Announcing engineer Geraldine Mertz. -Clyde Williamson will be the new engineer for the team. -Alvera Schiller II is a engineer with Example Corp. -Evalyn Buckridge has been a engineer for 14 years. -Chauncey Schinner has been a manager for 14 years. -Colleen Ortiz will be the new engineer for the team. -Layla Ernser is a engineer in the high tech industry. -Shanie Gutkowski is a manager. -Our latest new employee, Guido Rau, has been a manager in the industry for 4 years. -Ms. Fausto Mante has been an engineer for over a decade. -Jayne Smitham Sr., an manager, will be presenting the award. -Ms. Pattie Marks has been an engineer for over a decade. -Dock Kozey will be the new engineer for the team. -Nelle Crona joins us as an manager on the Example project. -Pamela O'Reilly is a manager in the high tech industry. -Glenna Cronin, an manager, will be presenting the award. -Rosendo Morissette is a engineer. -Vesta Johnson has been an manager for over a decade. -Ms. Don Lynch is a engineer in the high tech industry. -Brady Jacobson is a engineer in the high tech industry. -Monty Kulas has been a manager for 14 years. -Jettie Adams IV is a engineer. -Demetris Rutherford has been an manager for over a decade. -Our latest new employee, Lucious Crooks, has been a engineer in the industry for 4 years. -Dawn Renner IV will be the new engineer for the team. -Announcing manager Roger Skiles. -Announcing manager Twila Erdman. -Gregory Becker, an manager, will be presenting the award. -Announcing engineer Delaney Huels. -Hillary Braun has been a engineer for 14 years. -Juston Lesch has been an manager for over a decade. -Rowan Nikolaus is a engineer with Example Corp. -Alva Blanda joins us as an manager on the Example project. -Our latest new employee, Mr. Bria Marvin, has been a manager in the industry for 4 years. -Ashlee Kiehn is a engineer. -Janiya Waelchi is a manager in the high tech industry. -Jacey Rohan will be the new manager for the team. -Help me welcome our newest engineer, Hilma Dickinson. -Our latest new employee, Leanne Reichert II, has been a engineer in the industry for 4 years. -Ilene Johns has been an engineer for over a decade. -Help me welcome our newest engineer, Cyril Thiel. -Tina Tillman DVM is a engineer with Example Corp. -Announcing engineer Hobart Bechtelar. -Announcing manager Lemuel Lemke. -Niko Rolfson has been a manager for 14 years. -Stephanie Windler is a manager with Example Corp. -Foster Torphy is a manager with Example Corp. -Laverne MacGyver, an manager, will be presenting the award. -Riley Schowalter will be the new manager for the team. -Constance Welch is a manager in the high tech industry. -Julius Reinger joins us as an manager on the Example project. -Our latest new employee, Lourdes Grimes, has been a engineer in the industry for 4 years. +Announcing manager Gerson Parker. +Nickolas Little is a manager. +Alejandra Stiedemann V has been an manager for over a decade. +Eunice Leannon will be the new manager for the team. +Sunny Schmitt is a engineer in the high tech industry. +Anika Gutkowski is a engineer with Example Corp. +Delaney Fisher is retiring as a engineer. +Our latest new employee, Christian Maggio, has been a engineer in the industry for 4 years. +Mathias Hettinger I is retiring as a manager. +Our latest new employee, Elias Quitzon, has been a manager in the industry for 4 years. +Alexandra Wunsch has been an manager for over a decade. +Guido Kemmer joins us as an engineer on the Example project. +Sim Kemmer is a manager with Example Corp. +Help me welcome our newest manager, Vincenza Kertzmann. +Announcing engineer Skye Kuhn Jr.. +Kasandra Cartwright MD is a manager in the high tech industry. +Ettie Schimmel is a manager. +Karlee McCullough Jr. will be the new manager for the team. +Adeline Johnson is a manager with Example Corp. +Cory Morissette has been an manager for over a decade. +Brandy Abshire joins us as an manager on the Example project. +Announcing engineer Jerome Homenick. +Announcing manager Ms. Doris Ziemann. +Help me welcome our newest engineer, Ms. Marlene Larkin. +Amos Kuhlman, an manager, will be presenting the award. +Ana Ortiz II has been a manager for 14 years. +Announcing manager Jewell Konopelski. +Larry Effertz has been a engineer for 14 years. +Ms. Vernice Fay has been an engineer for over a decade. +Miss Dion Ratke is a manager. +Rachelle Lubowitz has been an manager for over a decade. +Rachael Moore is a manager in the high tech industry. +Sabrina Walsh will be the new engineer for the team. +Our latest new employee, Tamara Nicolas, has been a manager in the industry for 4 years. +Our latest new employee, Martin Lynch Sr., has been a manager in the industry for 4 years. +Kaya Wisozk is a manager in the high tech industry. +Our latest new employee, Mrs. Cassandra Robel, has been a engineer in the industry for 4 years. +Donny Schroeder has been an manager for over a decade. +Emmanuel Fay is a manager with Example Corp. +Jefferey Adams will be the new engineer for the team. +Godfrey Feest V, an manager, will be presenting the award. +Melyna Mertz has been a manager for 14 years. +Audreanne Wiza is a manager with Example Corp. +Jarvis O'Conner is a manager. +Mertie Sanford joins us as an manager on the Example project. +Help me welcome our newest engineer, Keshaun Altenwerth. +Aric Beier Sr. is a engineer with Example Corp. +Hoyt Rowe MD is a engineer with Example Corp. +Announcing engineer Aurelia Hintz. +Our latest new employee, Keon Stanton, has been a manager in the industry for 4 years. +Candace Wiegand is a manager with Example Corp. +Flavio Smith is retiring as a manager. +Our latest new employee, Mr. Zachery Toy, has been a manager in the industry for 4 years. +Kaleigh Murazik has been a engineer for 14 years. +Help me welcome our newest engineer, Pattie Kuvalis. +Serena Heidenreich has been a engineer for 14 years. +Our latest new employee, Simone Little, has been a manager in the industry for 4 years. +Ms. Carolina Mante has been a engineer for 14 years. +Julia Lemke has been a manager for 14 years. +Mrs. Meagan Fisher has been an manager for over a decade. +Molly Goldner joins us as an engineer on the Example project. +Announcing manager Mrs. Dax McGlynn. +Tyrique Harber has been an engineer for over a decade. +Mia Larkin II joins us as an manager on the Example project. +Winona Schoen, an manager, will be presenting the award. +Announcing engineer Dr. Antonette Cummings. +Our latest new employee, Mr. Sonya Cassin, has been a manager in the industry for 4 years. +Timothy Bartoletti DDS has been a manager for 14 years. +Our latest new employee, Winifred Reinger, has been a engineer in the industry for 4 years. +Announcing engineer Marcella Bahringer. +Rhea Kohler is a engineer. +Help me welcome our newest engineer, Alia McCullough. +Mr. Israel Gulgowski is a engineer. +Our latest new employee, Jana Wilkinson, has been a engineer in the industry for 4 years. +Marjory Fay will be the new manager for the team. +Alena Mueller is a engineer with Example Corp. +Help me welcome our newest engineer, Kristian Keebler. +Katheryn Dietrich is a manager with Example Corp. +Mya Grady has been an manager for over a decade. +Our latest new employee, Kyra Heidenreich V, has been a engineer in the industry for 4 years. +Announcing manager Dr. Cortez Thiel. +Shyanne Hirthe has been an engineer for over a decade. +Announcing engineer Herta Mohr. +Winfield Thompson is a engineer in the high tech industry. +Help me welcome our newest manager, Miss Bette Dooley. +Mrs. Freida Shanahan has been an engineer for over a decade. +Sunny Towne is a manager in the high tech industry. +Kayley Schneider joins us as an engineer on the Example project. +Announcing manager Anderson Hagenes. +Barrett Paucek Jr. is a manager. +Lillian Koss, an engineer, will be presenting the award. +Announcing engineer Lance Zieme. +Announcing engineer Van Hackett. +Cullen Schamberger is a engineer in the high tech industry. +Arvel Stanton joins us as an manager on the Example project. +Our latest new employee, Nedra Goldner, has been a manager in the industry for 4 years. +Ms. Zetta Lindgren is a manager in the high tech industry. +Help me welcome our newest engineer, Faye Ledner. +Trystan Hilll PhD has been an manager for over a decade. +Arnold Hermann Jr. will be the new engineer for the team. +Hank Towne is a manager in the high tech industry. +Monroe Schmeler has been a manager for 14 years. +Genesis Wintheiser II will be the new engineer for the team. +Angie O'Reilly has been an manager for over a decade. +Deshawn Reinger is a manager in the high tech industry. +Announcing engineer Mrs. Molly Lowe. +Kimberly Osinski is a manager in the high tech industry. +Napoleon Sauer is a engineer in the high tech industry. +Leonardo Champlin is a manager. +Anita Bartell is a manager in the high tech industry. +Mrs. Jennifer Buckridge has been a manager for 14 years. +Patrick Gulgowski is a manager with Example Corp. +Miss Brooks Christiansen, an manager, will be presenting the award. +Help me welcome our newest engineer, Ms. Alejandra Shields. +Callie Lynch will be the new engineer for the team. +Hyman Bruen will be the new engineer for the team. +Ms. Lera Grant, an engineer, will be presenting the award. +Help me welcome our newest manager, Laney West V. +Shyanne Price is a engineer. +Leanna Schoen is a engineer in the high tech industry. +Announcing engineer Clement Bayer. +Mrs. Colt Kozey is retiring as a manager. +Dr. Logan Dickinson will be the new engineer for the team. +Valentin Torp has been a manager for 14 years. +Kathlyn Bechtelar, an manager, will be presenting the award. +Vita Pollich has been a engineer for 14 years. +Benedict Flatley is a manager with Example Corp. +Our latest new employee, Dedrick Corkery V, has been a manager in the industry for 4 years. +Our latest new employee, Shea Kohler, has been a engineer in the industry for 4 years. +Tiana Mertz is a engineer in the high tech industry. +Margarett Kunze has been a engineer for 14 years. +Announcing manager Alan Bashirian. +Dr. Precious Murazik has been an manager for over a decade. +Ms. Karine Langosh has been a engineer for 14 years. +Help me welcome our newest engineer, Letha Skiles. +Help me welcome our newest engineer, Nikko Dooley. +Announcing engineer Filomena McKenzie. +Rosario Grant Sr., an engineer, will be presenting the award. +Gonzalo Douglas V, an engineer, will be presenting the award. +Lempi Pollich will be the new engineer for the team. +Help me welcome our newest engineer, Candida O'Reilly Sr.. +Dena Lind is a engineer with Example Corp. +Blair Renner is a manager with Example Corp. +Imani Roob joins us as an manager on the Example project. +Miss Drake Johns will be the new manager for the team. +Announcing manager Murl Jacobi. +Announcing manager Nicolette Prohaska. +Leif Quigley is a manager with Example Corp. +Hugh Gleichner III has been an manager for over a decade. +Neil Witting is a manager. +Ivah Moore is a engineer in the high tech industry. +Amira Bruen is retiring as a engineer. +Help me welcome our newest engineer, Loy Kerluke. +Our latest new employee, Cleta Berge, has been a manager in the industry for 4 years. +Asa Schaden has been a engineer for 14 years. +Announcing engineer Marlin Nitzsche. +Mellie Hansen has been an engineer for over a decade. +Pauline Willms has been an manager for over a decade. +Giovanna Marquardt is a manager in the high tech industry. +Kristian Conroy IV has been a manager for 14 years. +Announcing engineer Aiyana Hagenes. +Our latest new employee, Melisa Barton Jr., has been a manager in the industry for 4 years. +Help me welcome our newest manager, Robbie Bechtelar. +Citlalli Lind joins us as an engineer on the Example project. +Karlee Lubowitz is a manager in the high tech industry. +Isidro Jerde is a engineer with Example Corp. +Conor Lemke joins us as an manager on the Example project. +Murphy Rutherford joins us as an manager on the Example project. +Omari Schultz has been an manager for over a decade. +Selmer Labadie is a manager in the high tech industry. +Alverta Hilpert, an manager, will be presenting the award. +Our latest new employee, Ali Dooley III, has been a engineer in the industry for 4 years. +Benjamin Lubowitz is a manager. +Trenton Hilll will be the new manager for the team. +Bethel Carroll, an engineer, will be presenting the award. +Nick Lang will be the new manager for the team. +Ms. Chase Graham has been an manager for over a decade. +Announcing engineer Melany Buckridge PhD. +Ursula McGlynn joins us as an engineer on the Example project. +Jackie Kshlerin is a manager. +Jarvis Langosh is a engineer. +Bonnie Bednar will be the new manager for the team. +Announcing engineer Simeon Dickinson. +Annamarie VonRueden MD has been an manager for over a decade. +Ms. Hilton Hagenes has been an engineer for over a decade. +Jaqueline D'Amore has been an manager for over a decade. +Mario Bartell will be the new manager for the team. +Frances Kovacek has been an manager for over a decade. +Help me welcome our newest manager, Avery Lakin. +Brittany Flatley DDS, an engineer, will be presenting the award. +Violette Sauer has been an manager for over a decade. +Dorothy Farrell is a engineer in the high tech industry. +Miss Jordan Crooks is a manager in the high tech industry. +Jadyn West joins us as an manager on the Example project. +Mrs. Johanna Borer is a engineer with Example Corp. +Elyssa Crist joins us as an engineer on the Example project. +Our latest new employee, Carli Kohler, has been a manager in the industry for 4 years. +Dasia Dare has been a engineer for 14 years. +Lane Rath has been an manager for over a decade. +Davion Kunze, an engineer, will be presenting the award. +Vada Stoltenberg, an engineer, will be presenting the award. +London Hagenes has been an engineer for over a decade. +Albin Wintheiser will be the new manager for the team. +Help me welcome our newest engineer, Mr. Eloise Jones. +Rashawn Bechtelar is a manager with Example Corp. +Linwood Casper, an engineer, will be presenting the award. +Our latest new employee, Mr. Phyllis O'Reilly, has been a manager in the industry for 4 years. +Carrie Franecki is a manager in the high tech industry. +Our latest new employee, Mikel Daugherty I, has been a manager in the industry for 4 years. +Announcing manager Jorge Connelly IV. +Victor Cummings is a manager with Example Corp. +Felton Kris has been a manager for 14 years. +Barney Klein is a manager with Example Corp. +Help me welcome our newest manager, Verdie Strosin. +Announcing engineer Izabella Vandervort DDS. +Philip Hodkiewicz has been an manager for over a decade. +Mr. Carol Orn, an manager, will be presenting the award. +Ursula Cormier is a manager. +Ellen Hudson is retiring as a engineer. +Bobby Conn II has been an engineer for over a decade. +Announcing engineer Mathilde Bayer. +Ms. Marcus Ferry has been an engineer for over a decade. +Our latest new employee, Tyshawn Volkman, has been a manager in the industry for 4 years. +Our latest new employee, Ladarius Predovic, has been a engineer in the industry for 4 years. +Our latest new employee, Ally Parisian, has been a manager in the industry for 4 years. +Briana Reinger joins us as an manager on the Example project. +Piper Kutch has been a manager for 14 years. +Armand Ratke is a manager with Example Corp. +Stephon McLaughlin DDS joins us as an engineer on the Example project. +Cordell Cole is a engineer with Example Corp. +Earl Altenwerth is retiring as a engineer. +Our latest new employee, Garrison Lang Jr., has been a manager in the industry for 4 years. +Ahmad Heaney, an engineer, will be presenting the award. +Jed Pollich has been a manager for 14 years. +Help me welcome our newest manager, Hudson Wyman. +Dena Crist DVM has been an engineer for over a decade. +Help me welcome our newest manager, Gino Auer III. +Help me welcome our newest manager, Miss Xavier Farrell. +Announcing engineer Lawrence Considine. +Coby Lesch is a engineer in the high tech industry. +Hardy Mohr joins us as an manager on the Example project. +Dr. Tamara Ryan is retiring as a manager. +Alia Quigley PhD, an engineer, will be presenting the award. +Noelia Bergnaum is a engineer with Example Corp. +Dakota Brown is a engineer. +Elmo Rogahn is a engineer. +Roxanne Weimann joins us as an manager on the Example project. +Gay Langworth is a manager in the high tech industry. +Ellis Ledner joins us as an engineer on the Example project. +Weston Murazik will be the new engineer for the team. +Our latest new employee, Josefa Sauer, has been a manager in the industry for 4 years. +Our latest new employee, Ilene Wyman, has been a manager in the industry for 4 years. +Nia Gottlieb, an manager, will be presenting the award. +Our latest new employee, Crawford Wehner, has been a manager in the industry for 4 years. +Our latest new employee, Brenna Ritchie, has been a manager in the industry for 4 years. +Ms. Mariah Hudson joins us as an manager on the Example project. +Tyra Schneider Sr. has been an manager for over a decade. +Help me welcome our newest manager, Andreane Johns. +Help me welcome our newest engineer, Mrs. Mabel Rice. +Emelia Jaskolski PhD is a engineer. +Spencer Cole II is a engineer in the high tech industry. +Help me welcome our newest manager, Doris Stokes. +Lilian Erdman has been a engineer for 14 years. +Ms. Ramona Torp is retiring as a manager. +Ms. Lauryn Stark, an manager, will be presenting the award. +Israel Greenholt will be the new engineer for the team. +Ms. Boris Leannon is a manager with Example Corp. +Help me welcome our newest manager, Pearlie Swaniawski. +Delores Kilback joins us as an manager on the Example project. +Mariam Schultz is a engineer in the high tech industry. +Dimitri Mueller IV is a manager with Example Corp. +Maud Beahan will be the new manager for the team. +Fletcher Predovic DVM, an engineer, will be presenting the award. +Mrs. Joanne Aufderhar will be the new engineer for the team. +Miss Paul Lowe is retiring as a engineer. +Johnpaul Swift has been an manager for over a decade. +Miss Rowena Pouros is a engineer. +Benjamin Jenkins is a engineer. +Erwin Jenkins will be the new manager for the team. +Ms. Zula Turner, an engineer, will be presenting the award. +Rhiannon Lind is retiring as a manager. +Mrs. Garth Labadie will be the new manager for the team. +Mia King is a manager in the high tech industry. +Ewald Cronin is a engineer with Example Corp. +Carrie Roob III has been an manager for over a decade. +Help me welcome our newest manager, Clementina Schmeler. +Stewart Sipes II has been an engineer for over a decade. +Our latest new employee, Katelin D'Amore, has been a manager in the industry for 4 years. +Announcing manager Verlie Wiegand. +Marie Schaefer is a engineer with Example Corp. +Tillman Boehm is a engineer in the high tech industry. +Announcing manager Jacklyn Kohler. +Katrine Bruen will be the new manager for the team. +Our latest new employee, Lisa Gaylord, has been a engineer in the industry for 4 years. +Virginia Ruecker, an engineer, will be presenting the award. +Mrs. Garnett Christiansen joins us as an engineer on the Example project. +Anderson Weissnat has been an engineer for over a decade. +Help me welcome our newest engineer, Marie Armstrong. +Help me welcome our newest engineer, Prudence Fahey V. +Bria Medhurst will be the new manager for the team. +Announcing engineer Ms. Dewitt Bernhard. +Stanford Miller has been a manager for 14 years. +Freddie Treutel, an manager, will be presenting the award. +Oceane Bayer has been an manager for over a decade. +Ms. Adalberto Lindgren joins us as an manager on the Example project. +Our latest new employee, Meagan Bartoletti, has been a manager in the industry for 4 years. +Wilhelm Kutch, an engineer, will be presenting the award. +Our latest new employee, Khalid Farrell Sr., has been a manager in the industry for 4 years. +Help me welcome our newest engineer, Ms. Allison Zemlak. +Judson Rodriguez MD is a manager with Example Corp. +Help me welcome our newest manager, Mr. Alena Stanton. +Kaylin Kohler has been a engineer for 14 years. +Melany Price is a engineer with Example Corp. +Palma Brekke is a manager. +Help me welcome our newest manager, Ozella Larson. +Earnestine Sanford will be the new engineer for the team. +Kathryne Tromp has been an engineer for over a decade. +Ted Abernathy is a manager with Example Corp. +Our latest new employee, Nelle Waters, has been a engineer in the industry for 4 years. +Help me welcome our newest engineer, Dawn Kautzer. +Ms. Itzel Breitenberg has been an engineer for over a decade. +Meta Gibson is a manager in the high tech industry. +Haven Nitzsche joins us as an engineer on the Example project. +Our latest new employee, Antonetta Kilback I, has been a manager in the industry for 4 years. +Kiara Zboncak joins us as an manager on the Example project. +Our latest new employee, Leola Kris, has been a engineer in the industry for 4 years. +Our latest new employee, Mr. Conrad Hills, has been a manager in the industry for 4 years. +Alize Rogahn has been a engineer for 14 years. +Rudy Hamill is a manager in the high tech industry. +Ms. Celestino Turcotte joins us as an manager on the Example project. +Ms. Annetta Stracke has been an manager for over a decade. +Hailie Hudson is a engineer. +Mrs. Deven Moen joins us as an manager on the Example project. +Callie Larson is a manager with Example Corp. +Quentin Morar joins us as an engineer on the Example project. +Our latest new employee, Antonietta Kuhlman II, has been a manager in the industry for 4 years. +Announcing manager Cristal Shanahan DVM. +Cristopher Boyer joins us as an engineer on the Example project. +Keely Larkin joins us as an engineer on the Example project. +Royce Berge is a engineer with Example Corp. +Announcing engineer Benjamin Hilll. +Rashawn Bogan is retiring as a manager. +Ted Collier has been a manager for 14 years. +Alene Corwin has been an manager for over a decade. +David Hodkiewicz is a manager with Example Corp. +Garland Kuhic has been an manager for over a decade. +Sonya Wilderman is a manager. +Quinn Bradtke Jr. is a manager with Example Corp. +Help me welcome our newest engineer, Ellen Cummerata. +Mason Beatty, an manager, will be presenting the award. +Help me welcome our newest manager, Camylle Muller. +Hadley Upton has been a engineer for 14 years. +Keara Pfeffer is a engineer with Example Corp. +Angie Walsh is retiring as a manager. +Help me welcome our newest manager, Earl Cummings. +Ephraim Marks is retiring as a engineer. +Orval Reichert DDS is a engineer. +Help me welcome our newest engineer, Katheryn Gleichner. +Announcing engineer Jalyn Fay I. +Virginia Keebler DVM is retiring as a engineer. +Raphael Leffler is a engineer. +Juliana Stokes is a manager with Example Corp. +Casper Herman is a manager in the high tech industry. +Announcing manager Vladimir Reilly. +Erin Okuneva has been an manager for over a decade. +Martine White is retiring as a manager. +Tristian Mertz is a manager. +Mr. Sammy Schmitt is a engineer with Example Corp. +Alec Schuster joins us as an manager on the Example project. +Ms. Lorenza Walsh will be the new engineer for the team. +Help me welcome our newest engineer, Eldora Mayert. +Justina Breitenberg will be the new manager for the team. +Our latest new employee, Mariela Grady Jr., has been a engineer in the industry for 4 years. +Kevon Baumbach, an manager, will be presenting the award. +Our latest new employee, Wendell Hayes, has been a engineer in the industry for 4 years. +Pat Aufderhar is a engineer with Example Corp. +Bart Senger joins us as an manager on the Example project. +Kaitlyn Hahn is a engineer. +Our latest new employee, Mrs. Else Kozey, has been a manager in the industry for 4 years. +Mr. Ashton Batz will be the new engineer for the team. +Lilly Koepp has been a engineer for 14 years. +Mrs. Alfredo Cormier has been a manager for 14 years. +Gail Swaniawski DVM is a engineer. +Mrs. Valentina Wilderman has been an engineer for over a decade. +Paxton Doyle is a engineer in the high tech industry. +Jarret Block PhD joins us as an manager on the Example project. +Arnaldo Blanda joins us as an manager on the Example project. +Aiden Orn has been an manager for over a decade. +Florine West is a manager. +Help me welcome our newest manager, Sincere Harber. +Announcing engineer Joan Ziemann. +Katelyn Schultz has been an engineer for over a decade. +Maximus Gleichner is a manager in the high tech industry. +Announcing manager Elenor Schuster. +Marcelino Kautzer will be the new engineer for the team. +Lea Schulist Sr. has been a engineer for 14 years. +Jeanne Carter MD is a manager in the high tech industry. +Kayleigh Goldner DDS is a engineer in the high tech industry. +Hilario Denesik I is a manager with Example Corp. +Shirley Reichert has been a manager for 14 years. +Kris Dickens is a engineer. +Gene Frami, an manager, will be presenting the award. +Sadye Jacobson is a manager in the high tech industry. +Buck Cremin joins us as an engineer on the Example project. +Our latest new employee, Coty Lesch, has been a engineer in the industry for 4 years. +Dr. Kim Mertz has been an engineer for over a decade. +Randy Sanford will be the new manager for the team. +Help me welcome our newest manager, Levi Kirlin. +Announcing engineer Davin Yundt. +Help me welcome our newest engineer, Enola Bins. +Trent Kuvalis, an engineer, will be presenting the award. +Announcing engineer Jake Powlowski. +Ms. Ashlee Emmerich, an manager, will be presenting the award. +Hannah Davis, an engineer, will be presenting the award. +Wayne Champlin joins us as an engineer on the Example project. +Nikki Conn Jr. is a manager. +Announcing engineer Carli Bauch. +Norbert Feest is a manager in the high tech industry. +Robbie Wintheiser has been an engineer for over a decade. +Leta Abshire is a engineer in the high tech industry. +Fannie Walker is a engineer. +Heber Wilkinson is a manager with Example Corp. +Announcing manager Willie Bernier III. +Help me welcome our newest engineer, Orlando Price. +Brandt Schowalter is a manager. +Mohammed Stokes is retiring as a engineer. +Isai Mraz is a engineer in the high tech industry. +Kadin Lemke will be the new manager for the team. +Maribel Jerde has been an manager for over a decade. +Myrna Kessler is a manager. +Meredith Tremblay is retiring as a engineer. +Announcing engineer Mr. Jerad Schneider. +Announcing manager Lenny Pfeffer. +Carolyne Klocko DVM will be the new manager for the team. +Our latest new employee, Monica Schulist, has been a engineer in the industry for 4 years. +Anika Larson V is a manager. +Domenick Pacocha is retiring as a engineer. +Miss Harmon Pfannerstill has been a engineer for 14 years. +Announcing manager Mr. Annabell Pouros. +Dr. Brisa Stroman has been an manager for over a decade. +Help me welcome our newest manager, Jade Stoltenberg. +Miss Mario Wolff joins us as an engineer on the Example project. +Ms. Savannah Gaylord is retiring as a manager. +Dejah Jones, an manager, will be presenting the award. +Hector Kulas, an manager, will be presenting the award. +Graciela Goodwin will be the new manager for the team. +Jocelyn Sauer is a manager. +Miss Lew Hansen will be the new manager for the team. +Fannie Fay DDS is a engineer with Example Corp. +Dr. Jordan Klocko is a engineer with Example Corp. +Kathlyn Lynch is a manager in the high tech industry. +Leann Botsford has been an engineer for over a decade. +Ervin Larson has been a manager for 14 years. +Allie Von is a manager in the high tech industry. +Johanna Kohler III has been a manager for 14 years. +Our latest new employee, Hilbert Armstrong, has been a engineer in the industry for 4 years. +Tanner Balistreri IV has been an manager for over a decade. +Abagail Shields has been an engineer for over a decade. +Help me welcome our newest engineer, Gia Cremin. +Mrs. Buford Oberbrunner is retiring as a engineer. +Madelyn White is retiring as a manager. +Announcing manager Abdullah Effertz. +Reva Stark will be the new engineer for the team. +Camryn McKenzie will be the new manager for the team. +Juwan Pouros will be the new manager for the team. +Gene Cassin is a manager in the high tech industry. +Our latest new employee, Felicia Kunde, has been a engineer in the industry for 4 years. +Our latest new employee, Jeremie Anderson, has been a manager in the industry for 4 years. +Our latest new employee, Katheryn Hickle Jr., has been a manager in the industry for 4 years. +Our latest new employee, Edwina Hamill IV, has been a manager in the industry for 4 years. +Adriana Cassin DVM has been a manager for 14 years. +Nelda Rowe joins us as an engineer on the Example project. +Rodrigo Kulas V joins us as an engineer on the Example project. +Jaiden Williamson is a manager in the high tech industry. +Announcing manager Cristopher Williamson. +Mr. Jay Krajcik, an engineer, will be presenting the award. +Announcing manager Francesco Miller. +Brenna Reinger, an manager, will be presenting the award. +Mr. Mollie Stanton has been an manager for over a decade. +Coby Schowalter has been a engineer for 14 years. +Our latest new employee, Estefania Armstrong II, has been a manager in the industry for 4 years. +Announcing engineer Aimee Nienow. +Kimberly Batz will be the new engineer for the team. +Miss Sienna Pfannerstill is retiring as a engineer. +Johnathon Hammes is retiring as a engineer. +Julien Hansen is a manager in the high tech industry. +Mrs. Emerson Waelchi will be the new manager for the team. +Malcolm Streich is a manager with Example Corp. +Aurelio Lebsack is retiring as a engineer. +Juana Grady has been a engineer for 14 years. +Kiel Lakin is a manager in the high tech industry. +Our latest new employee, Sarai Keeling, has been a engineer in the industry for 4 years. +Emilia Crona has been a engineer for 14 years. +Georgianna Kris is a manager in the high tech industry. +Maida Heller is retiring as a manager. +Jena Feeney is a engineer with Example Corp. +Mabelle Keeling has been an manager for over a decade. +Chris Bergstrom has been an engineer for over a decade. +Our latest new employee, Audrey Block DDS, has been a engineer in the industry for 4 years. +Our latest new employee, Louvenia Kuhn, has been a engineer in the industry for 4 years. +Thomas O'Keefe has been a manager for 14 years. +Our latest new employee, Darby Klocko, has been a manager in the industry for 4 years. +Arlene Weimann, an manager, will be presenting the award. +Our latest new employee, Corbin Jones MD, has been a engineer in the industry for 4 years. +Lamar Mraz joins us as an engineer on the Example project. +Miss Onie Krajcik is a manager with Example Corp. +Kamille Schaefer, an manager, will be presenting the award. +Jack Borer, an engineer, will be presenting the award. +Reese Heaney has been a manager for 14 years. +Ilene Kovacek will be the new engineer for the team. +Trace Bailey will be the new engineer for the team. +Our latest new employee, Wava Donnelly, has been a engineer in the industry for 4 years. +Mona Lakin has been a manager for 14 years. +Weldon Heaney joins us as an manager on the Example project. +Norris Labadie has been a engineer for 14 years. +Bridgette Brown has been an manager for over a decade. +Osborne Kertzmann is a manager with Example Corp. +Announcing engineer Verlie Bruen. +Enrique Ullrich is a engineer with Example Corp. +Dr. Asia Purdy joins us as an engineer on the Example project. +Announcing manager Lindsey Predovic DDS. +Maxine Mosciski is retiring as a manager. +Sydni Stoltenberg is retiring as a engineer. +Paige Buckridge will be the new engineer for the team. +Miss Laverne Dach has been an manager for over a decade. +Murl Abshire is a manager in the high tech industry. +Lou Friesen is retiring as a manager. +Announcing engineer Keenan Fahey. +Ashleigh Schultz, an engineer, will be presenting the award. +Mrs. Keshaun Lesch has been an engineer for over a decade. +Announcing engineer Jeffrey Langosh. +Mckenzie Boyle, an engineer, will be presenting the award. +Hipolito Price PhD joins us as an manager on the Example project. +Lesley Adams III joins us as an engineer on the Example project. +Mya Howe is a engineer with Example Corp. +Nick Kutch Sr. has been a manager for 14 years. +Our latest new employee, Ms. Winfield Wilkinson, has been a engineer in the industry for 4 years. +Leopold Schulist is a engineer with Example Corp. +Announcing engineer Orval Prosacco. +Wilmer Mueller has been an manager for over a decade. +Karina Batz has been a manager for 14 years. +Our latest new employee, Luigi Abbott, has been a manager in the industry for 4 years. +Pamela Miller is a manager in the high tech industry. +Emelie Marquardt is a engineer with Example Corp. +Zola Beier is a manager in the high tech industry. +Mr. Elva Ritchie joins us as an engineer on the Example project. +Mrs. Otis Quitzon is a manager in the high tech industry. +Our latest new employee, Dr. Willow Jacobs, has been a manager in the industry for 4 years. +Cathryn Koss joins us as an manager on the Example project. +Ms. Alivia Ernser is retiring as a manager. +Timothy Mohr is a engineer. +Mrs. Jaylan Wuckert is retiring as a manager. +Emerald Waelchi is a engineer. +Vernon Heathcote is retiring as a manager. +Announcing engineer Lavinia Ruecker. +Mr. Quinn Altenwerth is retiring as a manager. +Alejandra Marks, an manager, will be presenting the award. +Mr. Leo Wuckert has been an manager for over a decade. +Jayce Schiller MD is a manager in the high tech industry. +Elenora Ebert Jr. will be the new engineer for the team. +Announcing manager Dr. Rose Wyman. +Help me welcome our newest manager, Wade Orn. +Iva Marks Sr., an manager, will be presenting the award. +Margaret Pouros will be the new manager for the team. +Barton Deckow has been an manager for over a decade. +Miss Lesly Balistreri will be the new engineer for the team. +Mr. Jacquelyn Reynolds joins us as an engineer on the Example project. +Announcing manager Doyle Heidenreich. +Announcing engineer Heidi Ruecker. +Mr. Alvis Moen joins us as an manager on the Example project. +Dr. Garnet Brown is a engineer. +Yolanda Beier is a engineer with Example Corp. +Soledad Macejkovic joins us as an engineer on the Example project. +Urban Lowe has been a engineer for 14 years. +Devyn Schmidt has been an manager for over a decade. +Barbara Flatley is a manager in the high tech industry. +Patsy Sanford PhD has been an engineer for over a decade. +Mrs. Rubye Blanda is a engineer. +Caleigh Klocko is a engineer. +Kali Dietrich has been an manager for over a decade. +Ms. Weldon Hudson is retiring as a engineer. +Help me welcome our newest engineer, Vallie Huel. +Sven O'Keefe III joins us as an engineer on the Example project. +Help me welcome our newest engineer, Gerardo Wehner Sr.. +Kyle Kirlin is a manager in the high tech industry. +Help me welcome our newest engineer, Marianne Berge Jr.. +Ms. Cristal Connelly is retiring as a manager. +Kailey Spinka has been an manager for over a decade. +Jeremie Morar has been an manager for over a decade. +Our latest new employee, Daija Lind, has been a engineer in the industry for 4 years. +Arvel McDermott is a engineer. +Dr. Nicholas Gorczany has been a manager for 14 years. +Anne Leuschke has been a manager for 14 years. +Gerda Cronin has been a engineer for 14 years. +Ms. Coty Rolfson is retiring as a engineer. +Announcing engineer Kareem Gerhold. +Announcing engineer Mrs. Nico Mann. +Corbin Bartell is a engineer in the high tech industry. +Theresa Gulgowski will be the new manager for the team. +Carmelo Boyer is retiring as a manager. +Elinore Schulist III joins us as an manager on the Example project. +Katarina Schultz joins us as an manager on the Example project. +Deven Rodriguez II is a manager in the high tech industry. +Miss Bruce Friesen is a manager in the high tech industry. +Marcelle Schowalter joins us as an manager on the Example project. +Albertha Murphy PhD has been an manager for over a decade. +Elmore Doyle is a engineer. +Our latest new employee, Reymundo Jaskolski, has been a engineer in the industry for 4 years. +Stephania Swaniawski I joins us as an manager on the Example project. +Stewart Veum has been an manager for over a decade. +Help me welcome our newest manager, Nathanael Bartell. +Retha Rempel is a engineer. +Isidro Aufderhar joins us as an manager on the Example project. +Florencio Mohr, an manager, will be presenting the award. +Zella Weimann is a engineer with Example Corp. +Khalid Macejkovic will be the new engineer for the team. +Our latest new employee, Geraldine Torp, has been a engineer in the industry for 4 years. +Presley Marks will be the new engineer for the team. +Mrs. Eve Bartoletti is retiring as a engineer. +Corine Schimmel, an manager, will be presenting the award. +Citlalli Goldner DDS, an manager, will be presenting the award. +Zakary Botsford, an manager, will be presenting the award. +Florida Reilly is retiring as a engineer. +Mr. Patsy Doyle joins us as an manager on the Example project. +Help me welcome our newest engineer, Emily Hayes II. +Dr. Johann Turcotte will be the new engineer for the team. +Dr. Darion Dietrich is a engineer in the high tech industry. +Norris Brekke is a manager in the high tech industry. +Janessa Marquardt joins us as an manager on the Example project. +Felicita Wintheiser MD has been an manager for over a decade. +Melyssa Muller, an engineer, will be presenting the award. +Vivienne Weissnat DDS has been a engineer for 14 years. +Ford Gerlach is a engineer. +Keenan Kertzmann is a engineer in the high tech industry. +Tobin Goyette joins us as an engineer on the Example project. +Help me welcome our newest manager, Cecilia Green. +Abe Fisher has been an manager for over a decade. +Mrs. Annabell Morissette is a manager. +Announcing manager Danny Kautzer III. +Gerard Cruickshank joins us as an engineer on the Example project. +Joy Okuneva DDS, an manager, will be presenting the award. +Ezequiel Macejkovic joins us as an engineer on the Example project. +Vernie Bradtke IV, an engineer, will be presenting the award. +Trycia Muller is a engineer. +Clotilde Ankunding is a engineer in the high tech industry. +Mr. Chaya Abshire, an engineer, will be presenting the award. +Announcing manager Hanna Roob. +Ronny Dietrich will be the new engineer for the team. +Derek Durgan DVM will be the new manager for the team. +Demetrius West will be the new manager for the team. +Macey Nikolaus is retiring as a manager. +Edison Gottlieb III is a manager in the high tech industry. +Bo Collins is a engineer with Example Corp. +Michaela Pagac PhD has been a manager for 14 years. +Mireille Kunde I is a engineer with Example Corp. +Duncan Kulas will be the new manager for the team. +Mrs. Xzavier Smitham is a engineer with Example Corp. +Announcing manager Mr. Adrianna Baumbach. +Shad Rolfson is a manager with Example Corp. +Mr. Dimitri Baumbach, an manager, will be presenting the award. +Our latest new employee, Samara Schultz, has been a manager in the industry for 4 years. +Mrs. Brant Kautzer is a manager in the high tech industry. +Tierra Greenholt is a engineer. +Our latest new employee, Otho Kub, has been a manager in the industry for 4 years. +Ana Harber will be the new manager for the team. +Our latest new employee, Ted Mertz Sr., has been a engineer in the industry for 4 years. +Uriel Zieme will be the new manager for the team. +Mr. Adaline Wolff has been an engineer for over a decade. +Help me welcome our newest manager, Mrs. Maurice Senger. +Ada Gleason has been an manager for over a decade. +Edwina Bernier DVM has been an engineer for over a decade. +Elva Homenick is retiring as a engineer. +Our latest new employee, Mrs. Shane Powlowski, has been a manager in the industry for 4 years. +Obie Nikolaus has been a manager for 14 years. +Ottis Jakubowski is a manager. +Mr. Armand Leannon is a engineer in the high tech industry. +Jaime Kuvalis joins us as an engineer on the Example project. +Help me welcome our newest engineer, Loyce VonRueden Jr.. +Evangeline Johns joins us as an manager on the Example project. +Federico Halvorson is a manager in the high tech industry. +Sylvester Gerlach is a manager in the high tech industry. +Our latest new employee, Ashly Wunsch V, has been a manager in the industry for 4 years. +Rowland Miller joins us as an manager on the Example project. +Bryon Kunde has been a manager for 14 years. +Denis Ernser has been an engineer for over a decade. +Jovanny O'Reilly DVM is a engineer with Example Corp. +Lazaro Hermiston is retiring as a manager. +Kelly Stehr is a engineer. +Oda Fadel is a engineer with Example Corp. +Percival Armstrong is a engineer. +Caleigh Schimmel joins us as an manager on the Example project. +Tyrique Pfeffer has been a engineer for 14 years. +Adell Leuschke is retiring as a engineer. +Dr. Cade Farrell will be the new engineer for the team. +Gisselle Doyle is retiring as a manager. +Lily Reinger will be the new manager for the team. +Jeffrey Gleason has been a engineer for 14 years. +Tad Huel, an engineer, will be presenting the award. +Connor Conn joins us as an manager on the Example project. +Mr. Cathrine Casper is a engineer with Example Corp. +Our latest new employee, Dr. Beryl Rempel, has been a manager in the industry for 4 years. +Carlie Steuber joins us as an manager on the Example project. +Rose Frami IV is a engineer. +Mr. Tyrel Pagac joins us as an manager on the Example project. +Our latest new employee, Morton Trantow, has been a engineer in the industry for 4 years. +Lola Ortiz has been an manager for over a decade. +Kelton Champlin has been a manager for 14 years. +Our latest new employee, Owen Mayert, has been a engineer in the industry for 4 years. +Johnny Witting joins us as an manager on the Example project. +Thea Rolfson will be the new manager for the team. +Reanna Schmidt has been an engineer for over a decade. +Help me welcome our newest manager, Ian Stehr. +Mr. Patsy Purdy will be the new manager for the team. +Brady Ritchie will be the new engineer for the team. +Our latest new employee, Thea Effertz, has been a manager in the industry for 4 years. +Gerry Veum has been a engineer for 14 years. +Corene Adams is retiring as a manager. +Julian Kutch has been a manager for 14 years. +Mrs. Joe Connelly is a engineer. +Announcing manager Adolphus Paucek. +Jasmin Ledner II is a engineer with Example Corp. +Dr. Osbaldo Beatty will be the new engineer for the team. +Dr. Mckenna Haag is a manager with Example Corp. +Help me welcome our newest engineer, Abdiel Connelly DVM. +Help me welcome our newest manager, Liliana Baumbach III. +Willard Kuvalis V is retiring as a engineer. +Carolyn Jaskolski Jr., an manager, will be presenting the award. +Reta Franecki is retiring as a engineer. +Percival O'Kon has been an engineer for over a decade. +Kamryn Rath is a manager with Example Corp. +Hailey Dooley is a manager in the high tech industry. +Mrs. Brycen West has been an engineer for over a decade. +Margarette Miller will be the new manager for the team. +Our latest new employee, Cristian Pagac, has been a manager in the industry for 4 years. +Rosalee Bechtelar, an manager, will be presenting the award. +Help me welcome our newest manager, Lessie Lesch. +Iva Hegmann joins us as an manager on the Example project. +Hallie Schroeder is a manager. +Mr. Lola Volkman is a engineer in the high tech industry. +Arianna Wolf DDS has been an manager for over a decade. +Elliot Trantow has been a manager for 14 years. +Darrion Rath PhD has been a engineer for 14 years. +Coralie Effertz V has been an engineer for over a decade. +Our latest new employee, Ms. Corrine Effertz, has been a engineer in the industry for 4 years. +Our latest new employee, Ellie Keebler, has been a engineer in the industry for 4 years. +Tyrese Pfeffer is a engineer in the high tech industry. +Announcing manager Jayce Roberts. +Isobel Veum is retiring as a engineer. +Raphaelle Breitenberg is retiring as a engineer. +Maudie Labadie I has been an engineer for over a decade. +Rosario Langosh MD has been a engineer for 14 years. +Raheem Mohr joins us as an manager on the Example project. +Avery Lind joins us as an engineer on the Example project. +Nichole Waters has been a engineer for 14 years. +Blaise Gislason I has been a manager for 14 years. +Everette D'Amore has been a engineer for 14 years. +Darwin Conroy is a manager in the high tech industry. +Abdullah Heathcote is retiring as a engineer. +Burnice Treutel has been an manager for over a decade. +Libbie O'Hara is a engineer with Example Corp. +Rowan Will has been an manager for over a decade. +Gudrun Gleason is retiring as a engineer. +Our latest new employee, Fannie Quitzon, has been a manager in the industry for 4 years. +Help me welcome our newest engineer, Terence Gutkowski. +Tyshawn Rowe is a manager. +Jaylan Sanford, an manager, will be presenting the award. +Camille Schaden DVM, an manager, will be presenting the award. +Ms. Blaze Emmerich will be the new manager for the team. +Announcing engineer Sonny Stoltenberg. +Elsie Jacobson is a manager. +Our latest new employee, London Jacobs, has been a manager in the industry for 4 years. +Mr. Hassie Kuhn is retiring as a engineer. +Raul Bogan MD is a engineer. +Adrian Abshire joins us as an manager on the Example project. +Our latest new employee, Golden Kreiger, has been a manager in the industry for 4 years. +Deven Stiedemann I, an manager, will be presenting the award. +Hilario Koepp PhD has been a manager for 14 years. +Maynard Herzog will be the new manager for the team. +Announcing manager Nathaniel Torp. +Courtney Strosin is a manager with Example Corp. +Emely Lowe is a engineer with Example Corp. +Vilma Weber is retiring as a engineer. +Announcing engineer Ms. Carlee Littel. +Help me welcome our newest engineer, Hayden Mills. +Ervin Schimmel is a engineer. +Gino Ortiz is a engineer in the high tech industry. +Help me welcome our newest manager, Amani Conroy. +Announcing manager Korbin Lowe. +Turner Bogan I will be the new engineer for the team. +Ms. Jabari Bauch has been an manager for over a decade. +Mrs. Breanne Morissette is a engineer in the high tech industry. +Our latest new employee, Crystel Doyle, has been a manager in the industry for 4 years. +Help me welcome our newest engineer, Isabel VonRueden. +Dayne Cremin is a engineer. +Help me welcome our newest manager, Waino Armstrong. +Deborah Armstrong is retiring as a manager. +Ashlynn Mante DVM is a engineer in the high tech industry. +Karlie Pollich Jr. joins us as an engineer on the Example project. +Maeve Schroeder is a manager in the high tech industry. +Hanna Fadel, an engineer, will be presenting the award. +Delphia O'Hara has been a manager for 14 years. +Jamir Hammes has been an engineer for over a decade. +Nigel Ortiz is retiring as a engineer. +Pauline Ritchie is a manager. +Nicholaus Toy has been an engineer for over a decade. +Freddy Okuneva joins us as an manager on the Example project. +Announcing engineer Brionna Fritsch. +Our latest new employee, Maiya Mills, has been a engineer in the industry for 4 years. +Alia Hoeger PhD has been a manager for 14 years. +Alvina Mertz is a engineer in the high tech industry. +Help me welcome our newest engineer, Raymundo Hintz. +Zack Stamm is a engineer in the high tech industry. +Dayne Klocko has been a manager for 14 years. +Announcing engineer Kyla Cremin. +Izabella Bernhard will be the new engineer for the team. +Zena Yundt is a engineer in the high tech industry. +Announcing manager Daron Schuppe. +Mr. Amira Marvin joins us as an manager on the Example project. +Boris Morar will be the new manager for the team. +Esperanza Batz is retiring as a engineer. +Dortha Macejkovic I has been an manager for over a decade. +Porter Dach V, an engineer, will be presenting the award. +Kailyn Flatley I is a manager with Example Corp. +Celine O'Keefe has been a manager for 14 years. +Obie Rodriguez, an manager, will be presenting the award. +Cade Gorczany joins us as an engineer on the Example project. +Myles Shanahan is a engineer. +Jayne Wiza will be the new engineer for the team. +Julius Huel has been an manager for over a decade. +Ms. Rowena Kihn, an engineer, will be presenting the award. +Ena Wehner is a manager with Example Corp. +Clovis Cartwright will be the new manager for the team. +Mr. Marcelo D'Amore is a manager. +Meggie Prosacco has been an engineer for over a decade. +Lisa Schamberger PhD has been an engineer for over a decade. +Mrs. Lyda Bayer, an manager, will be presenting the award. +Newell Hettinger joins us as an manager on the Example project. +Melany Wolf, an manager, will be presenting the award. +Emil Schaefer has been an manager for over a decade. +Samson Trantow has been a manager for 14 years. +Maida Marquardt is a engineer. +Johnpaul Howe MD is a manager with Example Corp. +Our latest new employee, Mrs. Adah Lubowitz, has been a engineer in the industry for 4 years. +Ms. Imelda Kohler, an manager, will be presenting the award. +Manuela Frami I, an engineer, will be presenting the award. +Noelia Padberg has been an manager for over a decade. +Una Eichmann DDS is a engineer. +Elta Nolan has been an engineer for over a decade. +Jaron Wyman is a engineer in the high tech industry. +Help me welcome our newest manager, Kayla Windler Sr.. +Mrs. Zetta Stiedemann joins us as an manager on the Example project. +Dr. Abner Adams is retiring as a manager. +Brenden Ortiz is retiring as a manager. +German Funk is a engineer. +Mr. Liliane Konopelski, an manager, will be presenting the award. +Jarrett Morar is a manager in the high tech industry. +Parker Huels is a manager in the high tech industry. +Mrs. Alvah Bayer is a engineer with Example Corp. +Wilhelm Parker Jr. is a engineer in the high tech industry. +Leo Mertz will be the new engineer for the team. +Corine Hills is a engineer. +Coy Raynor Sr. is a engineer. +Help me welcome our newest engineer, Mallie Streich DVM. +Daisy Hoeger is a manager in the high tech industry. +Michelle Hickle is a engineer with Example Corp. +Nolan Douglas will be the new engineer for the team. +Vivian Bernier is retiring as a engineer. +Announcing engineer Brigitte Toy. +Help me welcome our newest engineer, Aniyah Schoen. +Emmie Bins is a engineer. +Mazie Weimann, an manager, will be presenting the award. +Carole Aufderhar will be the new engineer for the team. +Bernhard O'Kon will be the new engineer for the team. +Flavio Moore Sr. is a manager with Example Corp. +Our latest new employee, Justina Wuckert, has been a engineer in the industry for 4 years. +Meredith Jones joins us as an engineer on the Example project. +Help me welcome our newest manager, Gene Champlin. +Clare Fay, an manager, will be presenting the award. +Lesly Johnston II is retiring as a manager. +Our latest new employee, Cristian Kling, has been a manager in the industry for 4 years. +Candido Littel is a manager. +Help me welcome our newest engineer, Mrs. Gregory Ritchie. +Lucio Sawayn is a manager with Example Corp. +Announcing manager Derick Rath DVM. +Gabriella Dietrich is a engineer in the high tech industry. +Lula Spencer DVM has been an engineer for over a decade. +Help me welcome our newest engineer, Horacio Kulas. +Davin Vandervort DDS has been a engineer for 14 years. +Ms. Avery Wisoky will be the new engineer for the team. +Talon Williamson MD, an manager, will be presenting the award. +Gerald Hahn has been a manager for 14 years. +Ettie Yost is retiring as a manager. +Abdullah Mosciski is retiring as a engineer. +Mrs. Marielle Bosco is a engineer in the high tech industry. +Kory Batz joins us as an manager on the Example project. +Noelia Kovacek, an engineer, will be presenting the award. +Kyleigh Nienow is a manager with Example Corp. +Alize Lind will be the new engineer for the team. +Ellsworth Altenwerth has been an engineer for over a decade. +Domenic Mayer has been a engineer for 14 years. +Ms. Geovanny Satterfield has been an engineer for over a decade. +Ella Daniel is a manager with Example Corp. +Kylee Bogisich PhD has been an engineer for over a decade. +Ryder Wilkinson Sr. is a manager in the high tech industry. +Help me welcome our newest engineer, Marina Schaefer. +Ms. Paige Bartell is a manager in the high tech industry. +Mitchel Murray has been an engineer for over a decade. +Tyler Quigley, an manager, will be presenting the award. +Veronica Kreiger has been a engineer for 14 years. +Halie Goldner has been an engineer for over a decade. +Ryder Lakin has been an engineer for over a decade. +Chloe Legros is a engineer. +Dariana O'Conner joins us as an engineer on the Example project. +Era Bins Jr. is a engineer with Example Corp. +Help me welcome our newest manager, Laila Reichert. +Dedrick Kuhic V has been a engineer for 14 years. +Haylee Price Jr. is retiring as a engineer. +Help me welcome our newest manager, Callie Shields. +Jarrod Fahey will be the new manager for the team. +Earlene Cremin is a engineer in the high tech industry. +Ellie Bergstrom will be the new manager for the team. +Armando Grady has been a engineer for 14 years. +Announcing engineer Dr. Lamar Hessel. +Joe Runolfsson Sr. is a engineer with Example Corp. +Announcing manager Manley Oberbrunner. +Meta Weissnat II has been an engineer for over a decade. +Dagmar Batz IV is a engineer with Example Corp. +Our latest new employee, Susie Bayer, has been a engineer in the industry for 4 years. +Help me welcome our newest engineer, Randi Howell. +Joanne Rau is a manager in the high tech industry. +Buck Stark is a engineer. +Shane Donnelly is a engineer with Example Corp. +Quincy Casper V is retiring as a engineer. +Lafayette Grimes will be the new manager for the team. +Mrs. Jody Beer will be the new engineer for the team. +Miss Corene Schamberger has been an engineer for over a decade. +Dr. Jesse Baumbach is retiring as a engineer. +Brenna Quigley Jr. is a manager. +Ms. Viviane Bins joins us as an engineer on the Example project. +Help me welcome our newest manager, Edgar Johnson. +Mr. Tracy Beier has been a engineer for 14 years. +Juvenal Ortiz has been a engineer for 14 years. +Dr. Nat Pagac is retiring as a manager. +Our latest new employee, Julio Mitchell, has been a engineer in the industry for 4 years. +Help me welcome our newest engineer, Clarissa Mraz Jr.. +Dustin Grady will be the new manager for the team. +Brennon Bayer is a engineer. +Mrs. Birdie Nienow has been a manager for 14 years. +Announcing engineer Randal Sauer Sr.. +Verda Kozey will be the new engineer for the team. +Chesley Hickle is a engineer with Example Corp. +Miss Rico Block has been a engineer for 14 years. +Gaetano Lindgren has been a manager for 14 years. +Hope Hauck will be the new engineer for the team. +Percival O'Connell is a engineer in the high tech industry. +Melyna Leuschke is a manager. +Our latest new employee, Adan Collins, has been a manager in the industry for 4 years. +Daryl Bashirian has been an engineer for over a decade. +Kara Welch, an manager, will be presenting the award. +Norberto Swift has been an engineer for over a decade. +Effie Champlin Jr. will be the new engineer for the team. +Dr. Julia Metz joins us as an manager on the Example project. +Janice Witting is a manager in the high tech industry. +Bruce Eichmann, an manager, will be presenting the award. +Isobel Swift is a engineer. +Earnestine Mayer MD has been an manager for over a decade. +Michele Bashirian is a engineer in the high tech industry. +Janick Crona is a engineer in the high tech industry. +Hank Fisher III has been a manager for 14 years. +Miss Aliya Skiles is a engineer. +Herbert Orn has been an manager for over a decade. +Our latest new employee, Aglae Baumbach, has been a engineer in the industry for 4 years. +Gayle Carter will be the new engineer for the team. +Announcing manager Creola Kautzer. +Announcing manager Theresa Hauck. +Ms. Patience Wintheiser, an manager, will be presenting the award. +Announcing engineer Sebastian Rutherford DDS. +Ara Pollich I has been a engineer for 14 years. +Carlos Baumbach has been an manager for over a decade. +Hulda Schroeder DVM is a engineer with Example Corp. +Stanton Torp joins us as an manager on the Example project. +Our latest new employee, Ceasar Franecki, has been a manager in the industry for 4 years. +Lelah Miller is a engineer. +Wyman Schultz is a manager in the high tech industry. +Mae Harris V will be the new manager for the team. +Help me welcome our newest engineer, Hortense Koelpin. +Our latest new employee, Wilmer Deckow, has been a engineer in the industry for 4 years. +Zaria Ferry has been an engineer for over a decade. +Help me welcome our newest engineer, Pierre Cronin. +Ms. Brenna Leffler is a engineer with Example Corp. +Announcing engineer Dr. Keara Price. +Announcing manager Jane Schroeder. +Reymundo Heathcote has been a manager for 14 years. +Elton Schiller II is a manager with Example Corp. +Irma Blanda is a manager with Example Corp. +Mireya Turner, an manager, will be presenting the award. +Dawson Streich will be the new manager for the team. +Gianni Cassin is a manager in the high tech industry. +Johnathan Kuhic V joins us as an manager on the Example project. +Our latest new employee, Lacey Sawayn, has been a engineer in the industry for 4 years. diff --git a/internal/service/comprehend/test-fixtures/entity_recognizer/entitylist.csv b/internal/service/comprehend/test-fixtures/entity_recognizer/entitylist.csv index 2362643bb24..f1080f9213f 100644 --- a/internal/service/comprehend/test-fixtures/entity_recognizer/entitylist.csv +++ b/internal/service/comprehend/test-fixtures/entity_recognizer/entitylist.csv @@ -1,1001 +1,1001 @@ Text,Type -Rachelle Lubowitz MD,MANAGER -Donnell Schulist,MANAGER -Adelbert Ankunding,MANAGER -Mr. Francesca Moen,MANAGER -Shanna Roberts,ENGINEER -Rachel Williamson,ENGINEER -Tianna Keebler PhD,ENGINEER -Rowena Sipes III,ENGINEER -Mortimer Mueller Sr.,MANAGER -Mack Bashirian,MANAGER -Mohammad Bayer I,MANAGER -Fleta Raynor DDS,ENGINEER -Duane Kassulke,MANAGER -Clarissa Jenkins,MANAGER -Chester Bradtke,ENGINEER -Alexandrine Watsica,MANAGER -Mr. Donnie Osinski,MANAGER -Dudley Jacobs DDS,MANAGER -Katrina Senger,MANAGER -Andy Hammes DVM,MANAGER -Jaylan Klocko,MANAGER -Mr. Kaleigh Champlin,ENGINEER -Nick Predovic,MANAGER -Miss Nichole Farrell,ENGINEER -Elliot Ziemann,MANAGER -Mr. Zoila Howell,MANAGER -Max Mertz,MANAGER -Edwardo Nolan V,ENGINEER -Mya McGlynn,ENGINEER -Annette Sauer,MANAGER -Rocky Heathcote,MANAGER -Alize Collier,MANAGER -Lea Romaguera,ENGINEER -Jacinto Simonis PhD,MANAGER -Marques Morar,MANAGER -Enoch Grant,MANAGER -Liam Beer,ENGINEER -Dr. Tad Hilpert,MANAGER -Rhianna Lang,MANAGER -Mr. Florian White,ENGINEER -Helmer O'Connell,MANAGER -Raoul Strosin,MANAGER -Deshaun Wilkinson,MANAGER -Clementina Robel,MANAGER -Hattie Schroeder,MANAGER -Pauline Eichmann,ENGINEER -Isabella Sauer,ENGINEER -Jaclyn Gaylord,ENGINEER -Nyah Stiedemann,ENGINEER -Keith Kris PhD,MANAGER -Antwon Wyman,MANAGER -Mrs. Kelsie Champlin,MANAGER -Tamia Grady,MANAGER -Lue Russel,ENGINEER -Kenyon Reinger,ENGINEER -Akeem O'Hara,ENGINEER -Mrs. Thomas Funk,MANAGER -Bryce Hyatt MD,ENGINEER -Rebeca Christiansen,MANAGER -Alene Russel,MANAGER -Mikel Upton,ENGINEER -Ms. Demond Walker,MANAGER -Taya Leuschke MD,ENGINEER -Dr. Deon Huel,MANAGER -Josie Simonis Sr.,MANAGER -Dr. Leonie Cummerata,ENGINEER -Jamar Bins II,MANAGER -Samara Ledner,MANAGER -Coy Weber III,ENGINEER -Miss Kyler Grant,ENGINEER -Maya Little,ENGINEER -Brycen Walsh PhD,ENGINEER -Regan Parker,ENGINEER -Mrs. Jayson Kohler,ENGINEER -Erika Balistreri IV,MANAGER -Lavinia Rolfson,ENGINEER -Seamus Wintheiser,ENGINEER -Floy Reilly,MANAGER -Dr. Omer Leannon,MANAGER -Paxton Hansen,ENGINEER -Darlene Volkman,MANAGER -Lamar Hermiston,ENGINEER -Johnny Steuber MD,ENGINEER -Amanda Harris,ENGINEER -Meagan Herman MD,MANAGER -Avis Stark IV,ENGINEER -Fay Rodriguez DDS,MANAGER -Leif Quigley,ENGINEER -Charlie Davis,MANAGER -Jaylon Daugherty IV,MANAGER -Tyreek Shields MD,ENGINEER -Cassandre Stracke,ENGINEER -Tommie Wilkinson Jr.,ENGINEER -Anibal Gorczany,ENGINEER -Nova Grady III,MANAGER -Dr. Rosemarie Olson,MANAGER -Amparo O'Reilly,MANAGER -Keely Douglas,ENGINEER -Mr. Immanuel Collins,MANAGER -Trisha McKenzie II,ENGINEER -Leola Considine,MANAGER -Bert Rodriguez,MANAGER -Cassandre Schroeder,ENGINEER -Cory Hackett PhD,MANAGER -Mr. Bertram Willms,MANAGER -Peggie Jerde,ENGINEER -Iva Lueilwitz,MANAGER -Miss Arvilla Hermiston,ENGINEER -Rickey Schmidt,MANAGER -Emery Denesik Jr.,MANAGER -Roel Bernhard,MANAGER +Gerson Parker,MANAGER +Nickolas Little,MANAGER +Alejandra Stiedemann V,MANAGER +Eunice Leannon,MANAGER +Sunny Schmitt,ENGINEER +Anika Gutkowski,ENGINEER +Delaney Fisher,ENGINEER +Christian Maggio,ENGINEER +Mathias Hettinger I,MANAGER +Elias Quitzon,MANAGER +Alexandra Wunsch,MANAGER +Guido Kemmer,ENGINEER +Sim Kemmer,MANAGER +Vincenza Kertzmann,MANAGER +Skye Kuhn Jr.,ENGINEER +Kasandra Cartwright MD,MANAGER +Ettie Schimmel,MANAGER +Karlee McCullough Jr.,MANAGER +Adeline Johnson,MANAGER +Cory Morissette,MANAGER +Brandy Abshire,MANAGER +Jerome Homenick,ENGINEER +Ms. Doris Ziemann,MANAGER +Ms. Marlene Larkin,ENGINEER +Amos Kuhlman,MANAGER +Ana Ortiz II,MANAGER +Jewell Konopelski,MANAGER +Larry Effertz,ENGINEER +Ms. Vernice Fay,ENGINEER +Miss Dion Ratke,MANAGER +Rachelle Lubowitz,MANAGER +Rachael Moore,MANAGER +Sabrina Walsh,ENGINEER +Tamara Nicolas,MANAGER +Martin Lynch Sr.,MANAGER +Kaya Wisozk,MANAGER +Mrs. Cassandra Robel,ENGINEER +Donny Schroeder,MANAGER +Emmanuel Fay,MANAGER +Jefferey Adams,ENGINEER +Godfrey Feest V,MANAGER +Melyna Mertz,MANAGER +Audreanne Wiza,MANAGER +Jarvis O'Conner,MANAGER +Mertie Sanford,MANAGER +Keshaun Altenwerth,ENGINEER +Aric Beier Sr.,ENGINEER +Hoyt Rowe MD,ENGINEER +Aurelia Hintz,ENGINEER +Keon Stanton,MANAGER +Candace Wiegand,MANAGER +Flavio Smith,MANAGER +Mr. Zachery Toy,MANAGER +Kaleigh Murazik,ENGINEER +Pattie Kuvalis,ENGINEER +Serena Heidenreich,ENGINEER +Simone Little,MANAGER +Ms. Carolina Mante,ENGINEER +Julia Lemke,MANAGER +Mrs. Meagan Fisher,MANAGER +Molly Goldner,ENGINEER +Mrs. Dax McGlynn,MANAGER +Tyrique Harber,ENGINEER +Mia Larkin II,MANAGER +Winona Schoen,MANAGER +Dr. Antonette Cummings,ENGINEER +Mr. Sonya Cassin,MANAGER +Timothy Bartoletti DDS,MANAGER +Winifred Reinger,ENGINEER +Marcella Bahringer,ENGINEER +Rhea Kohler,ENGINEER +Alia McCullough,ENGINEER +Mr. Israel Gulgowski,ENGINEER +Jana Wilkinson,ENGINEER +Marjory Fay,MANAGER +Alena Mueller,ENGINEER +Kristian Keebler,ENGINEER +Katheryn Dietrich,MANAGER +Mya Grady,MANAGER +Kyra Heidenreich V,ENGINEER +Dr. Cortez Thiel,MANAGER +Shyanne Hirthe,ENGINEER +Herta Mohr,ENGINEER +Winfield Thompson,ENGINEER +Miss Bette Dooley,MANAGER +Mrs. Freida Shanahan,ENGINEER +Sunny Towne,MANAGER +Kayley Schneider,ENGINEER +Anderson Hagenes,MANAGER +Barrett Paucek Jr.,MANAGER +Lillian Koss,ENGINEER +Lance Zieme,ENGINEER +Van Hackett,ENGINEER +Cullen Schamberger,ENGINEER +Arvel Stanton,MANAGER +Nedra Goldner,MANAGER +Ms. Zetta Lindgren,MANAGER +Faye Ledner,ENGINEER +Trystan Hilll PhD,MANAGER +Arnold Hermann Jr.,ENGINEER +Hank Towne,MANAGER +Monroe Schmeler,MANAGER +Genesis Wintheiser II,ENGINEER +Angie O'Reilly,MANAGER +Deshawn Reinger,MANAGER +Mrs. Molly Lowe,ENGINEER +Kimberly Osinski,MANAGER +Napoleon Sauer,ENGINEER +Leonardo Champlin,MANAGER +Anita Bartell,MANAGER +Mrs. Jennifer Buckridge,MANAGER +Patrick Gulgowski,MANAGER +Miss Brooks Christiansen,MANAGER +Ms. Alejandra Shields,ENGINEER +Callie Lynch,ENGINEER +Hyman Bruen,ENGINEER +Ms. Lera Grant,ENGINEER +Laney West V,MANAGER +Shyanne Price,ENGINEER +Leanna Schoen,ENGINEER +Clement Bayer,ENGINEER +Mrs. Colt Kozey,MANAGER +Dr. Logan Dickinson,ENGINEER +Valentin Torp,MANAGER +Kathlyn Bechtelar,MANAGER +Vita Pollich,ENGINEER +Benedict Flatley,MANAGER +Dedrick Corkery V,MANAGER +Shea Kohler,ENGINEER +Tiana Mertz,ENGINEER +Margarett Kunze,ENGINEER +Alan Bashirian,MANAGER +Dr. Precious Murazik,MANAGER +Ms. Karine Langosh,ENGINEER +Letha Skiles,ENGINEER +Nikko Dooley,ENGINEER +Filomena McKenzie,ENGINEER +Rosario Grant Sr.,ENGINEER +Gonzalo Douglas V,ENGINEER +Lempi Pollich,ENGINEER +Candida O'Reilly Sr.,ENGINEER +Dena Lind,ENGINEER +Blair Renner,MANAGER +Imani Roob,MANAGER +Miss Drake Johns,MANAGER +Murl Jacobi,MANAGER +Nicolette Prohaska,MANAGER +Leif Quigley,MANAGER +Hugh Gleichner III,MANAGER +Neil Witting,MANAGER +Ivah Moore,ENGINEER +Amira Bruen,ENGINEER +Loy Kerluke,ENGINEER +Cleta Berge,MANAGER +Asa Schaden,ENGINEER +Marlin Nitzsche,ENGINEER +Mellie Hansen,ENGINEER +Pauline Willms,MANAGER +Giovanna Marquardt,MANAGER +Kristian Conroy IV,MANAGER +Aiyana Hagenes,ENGINEER +Melisa Barton Jr.,MANAGER +Robbie Bechtelar,MANAGER +Citlalli Lind,ENGINEER +Karlee Lubowitz,MANAGER +Isidro Jerde,ENGINEER +Conor Lemke,MANAGER +Murphy Rutherford,MANAGER +Omari Schultz,MANAGER +Selmer Labadie,MANAGER +Alverta Hilpert,MANAGER +Ali Dooley III,ENGINEER +Benjamin Lubowitz,MANAGER +Trenton Hilll,MANAGER +Bethel Carroll,ENGINEER +Nick Lang,MANAGER +Ms. Chase Graham,MANAGER +Melany Buckridge PhD,ENGINEER +Ursula McGlynn,ENGINEER +Jackie Kshlerin,MANAGER +Jarvis Langosh,ENGINEER +Bonnie Bednar,MANAGER +Simeon Dickinson,ENGINEER +Annamarie VonRueden MD,MANAGER +Ms. Hilton Hagenes,ENGINEER +Jaqueline D'Amore,MANAGER +Mario Bartell,MANAGER +Frances Kovacek,MANAGER +Avery Lakin,MANAGER +Brittany Flatley DDS,ENGINEER +Violette Sauer,MANAGER +Dorothy Farrell,ENGINEER +Miss Jordan Crooks,MANAGER +Jadyn West,MANAGER +Mrs. Johanna Borer,ENGINEER +Elyssa Crist,ENGINEER +Carli Kohler,MANAGER +Dasia Dare,ENGINEER +Lane Rath,MANAGER +Davion Kunze,ENGINEER +Vada Stoltenberg,ENGINEER +London Hagenes,ENGINEER +Albin Wintheiser,MANAGER +Mr. Eloise Jones,ENGINEER +Rashawn Bechtelar,MANAGER +Linwood Casper,ENGINEER +Mr. Phyllis O'Reilly,MANAGER +Carrie Franecki,MANAGER +Mikel Daugherty I,MANAGER +Jorge Connelly IV,MANAGER +Victor Cummings,MANAGER +Felton Kris,MANAGER +Barney Klein,MANAGER +Verdie Strosin,MANAGER +Izabella Vandervort DDS,ENGINEER +Philip Hodkiewicz,MANAGER +Mr. Carol Orn,MANAGER +Ursula Cormier,MANAGER +Ellen Hudson,ENGINEER +Bobby Conn II,ENGINEER +Mathilde Bayer,ENGINEER +Ms. Marcus Ferry,ENGINEER +Tyshawn Volkman,MANAGER +Ladarius Predovic,ENGINEER +Ally Parisian,MANAGER +Briana Reinger,MANAGER +Piper Kutch,MANAGER +Armand Ratke,MANAGER +Stephon McLaughlin DDS,ENGINEER +Cordell Cole,ENGINEER +Earl Altenwerth,ENGINEER +Garrison Lang Jr.,MANAGER +Ahmad Heaney,ENGINEER +Jed Pollich,MANAGER +Hudson Wyman,MANAGER +Dena Crist DVM,ENGINEER +Gino Auer III,MANAGER +Miss Xavier Farrell,MANAGER +Lawrence Considine,ENGINEER +Coby Lesch,ENGINEER +Hardy Mohr,MANAGER +Dr. Tamara Ryan,MANAGER +Alia Quigley PhD,ENGINEER +Noelia Bergnaum,ENGINEER +Dakota Brown,ENGINEER +Elmo Rogahn,ENGINEER +Roxanne Weimann,MANAGER +Gay Langworth,MANAGER +Ellis Ledner,ENGINEER +Weston Murazik,ENGINEER +Josefa Sauer,MANAGER +Ilene Wyman,MANAGER +Nia Gottlieb,MANAGER +Crawford Wehner,MANAGER +Brenna Ritchie,MANAGER +Ms. Mariah Hudson,MANAGER +Tyra Schneider Sr.,MANAGER +Andreane Johns,MANAGER +Mrs. Mabel Rice,ENGINEER +Emelia Jaskolski PhD,ENGINEER +Spencer Cole II,ENGINEER +Doris Stokes,MANAGER +Lilian Erdman,ENGINEER +Ms. Ramona Torp,MANAGER +Ms. Lauryn Stark,MANAGER +Israel Greenholt,ENGINEER +Ms. Boris Leannon,MANAGER +Pearlie Swaniawski,MANAGER +Delores Kilback,MANAGER +Mariam Schultz,ENGINEER +Dimitri Mueller IV,MANAGER +Maud Beahan,MANAGER +Fletcher Predovic DVM,ENGINEER +Mrs. Joanne Aufderhar,ENGINEER +Miss Paul Lowe,ENGINEER +Johnpaul Swift,MANAGER +Miss Rowena Pouros,ENGINEER +Benjamin Jenkins,ENGINEER +Erwin Jenkins,MANAGER +Ms. Zula Turner,ENGINEER +Rhiannon Lind,MANAGER +Mrs. Garth Labadie,MANAGER +Mia King,MANAGER +Ewald Cronin,ENGINEER +Carrie Roob III,MANAGER +Clementina Schmeler,MANAGER +Stewart Sipes II,ENGINEER +Katelin D'Amore,MANAGER +Verlie Wiegand,MANAGER +Marie Schaefer,ENGINEER +Tillman Boehm,ENGINEER +Jacklyn Kohler,MANAGER +Katrine Bruen,MANAGER +Lisa Gaylord,ENGINEER +Virginia Ruecker,ENGINEER +Mrs. Garnett Christiansen,ENGINEER +Anderson Weissnat,ENGINEER +Marie Armstrong,ENGINEER +Prudence Fahey V,ENGINEER +Bria Medhurst,MANAGER +Ms. Dewitt Bernhard,ENGINEER +Stanford Miller,MANAGER +Freddie Treutel,MANAGER +Oceane Bayer,MANAGER +Ms. Adalberto Lindgren,MANAGER +Meagan Bartoletti,MANAGER +Wilhelm Kutch,ENGINEER +Khalid Farrell Sr.,MANAGER +Ms. Allison Zemlak,ENGINEER +Judson Rodriguez MD,MANAGER +Mr. Alena Stanton,MANAGER +Kaylin Kohler,ENGINEER +Melany Price,ENGINEER +Palma Brekke,MANAGER +Ozella Larson,MANAGER +Earnestine Sanford,ENGINEER +Kathryne Tromp,ENGINEER +Ted Abernathy,MANAGER +Nelle Waters,ENGINEER +Dawn Kautzer,ENGINEER +Ms. Itzel Breitenberg,ENGINEER +Meta Gibson,MANAGER +Haven Nitzsche,ENGINEER +Antonetta Kilback I,MANAGER +Kiara Zboncak,MANAGER +Leola Kris,ENGINEER +Mr. Conrad Hills,MANAGER +Alize Rogahn,ENGINEER +Rudy Hamill,MANAGER +Ms. Celestino Turcotte,MANAGER +Ms. Annetta Stracke,MANAGER +Hailie Hudson,ENGINEER +Mrs. Deven Moen,MANAGER +Callie Larson,MANAGER +Quentin Morar,ENGINEER +Antonietta Kuhlman II,MANAGER +Cristal Shanahan DVM,MANAGER +Cristopher Boyer,ENGINEER +Keely Larkin,ENGINEER +Royce Berge,ENGINEER +Benjamin Hilll,ENGINEER +Rashawn Bogan,MANAGER +Ted Collier,MANAGER +Alene Corwin,MANAGER +David Hodkiewicz,MANAGER +Garland Kuhic,MANAGER +Sonya Wilderman,MANAGER +Quinn Bradtke Jr.,MANAGER +Ellen Cummerata,ENGINEER +Mason Beatty,MANAGER +Camylle Muller,MANAGER +Hadley Upton,ENGINEER +Keara Pfeffer,ENGINEER +Angie Walsh,MANAGER +Earl Cummings,MANAGER +Ephraim Marks,ENGINEER +Orval Reichert DDS,ENGINEER +Katheryn Gleichner,ENGINEER +Jalyn Fay I,ENGINEER +Virginia Keebler DVM,ENGINEER +Raphael Leffler,ENGINEER +Juliana Stokes,MANAGER +Casper Herman,MANAGER +Vladimir Reilly,MANAGER +Erin Okuneva,MANAGER +Martine White,MANAGER +Tristian Mertz,MANAGER +Mr. Sammy Schmitt,ENGINEER +Alec Schuster,MANAGER +Ms. Lorenza Walsh,ENGINEER +Eldora Mayert,ENGINEER +Justina Breitenberg,MANAGER +Mariela Grady Jr.,ENGINEER +Kevon Baumbach,MANAGER +Wendell Hayes,ENGINEER +Pat Aufderhar,ENGINEER +Bart Senger,MANAGER +Kaitlyn Hahn,ENGINEER +Mrs. Else Kozey,MANAGER +Mr. Ashton Batz,ENGINEER +Lilly Koepp,ENGINEER +Mrs. Alfredo Cormier,MANAGER +Gail Swaniawski DVM,ENGINEER +Mrs. Valentina Wilderman,ENGINEER +Paxton Doyle,ENGINEER +Jarret Block PhD,MANAGER +Arnaldo Blanda,MANAGER +Aiden Orn,MANAGER +Florine West,MANAGER +Sincere Harber,MANAGER +Joan Ziemann,ENGINEER +Katelyn Schultz,ENGINEER +Maximus Gleichner,MANAGER +Elenor Schuster,MANAGER +Marcelino Kautzer,ENGINEER +Lea Schulist Sr.,ENGINEER +Jeanne Carter MD,MANAGER +Kayleigh Goldner DDS,ENGINEER +Hilario Denesik I,MANAGER +Shirley Reichert,MANAGER +Kris Dickens,ENGINEER +Gene Frami,MANAGER +Sadye Jacobson,MANAGER +Buck Cremin,ENGINEER +Coty Lesch,ENGINEER +Dr. Kim Mertz,ENGINEER +Randy Sanford,MANAGER +Levi Kirlin,MANAGER +Davin Yundt,ENGINEER +Enola Bins,ENGINEER +Trent Kuvalis,ENGINEER +Jake Powlowski,ENGINEER +Ms. Ashlee Emmerich,MANAGER +Hannah Davis,ENGINEER +Wayne Champlin,ENGINEER +Nikki Conn Jr.,MANAGER +Carli Bauch,ENGINEER +Norbert Feest,MANAGER +Robbie Wintheiser,ENGINEER +Leta Abshire,ENGINEER +Fannie Walker,ENGINEER +Heber Wilkinson,MANAGER +Willie Bernier III,MANAGER +Orlando Price,ENGINEER +Brandt Schowalter,MANAGER +Mohammed Stokes,ENGINEER +Isai Mraz,ENGINEER +Kadin Lemke,MANAGER +Maribel Jerde,MANAGER +Myrna Kessler,MANAGER +Meredith Tremblay,ENGINEER +Mr. Jerad Schneider,ENGINEER +Lenny Pfeffer,MANAGER +Carolyne Klocko DVM,MANAGER +Monica Schulist,ENGINEER +Anika Larson V,MANAGER +Domenick Pacocha,ENGINEER +Miss Harmon Pfannerstill,ENGINEER +Mr. Annabell Pouros,MANAGER +Dr. Brisa Stroman,MANAGER +Jade Stoltenberg,MANAGER +Miss Mario Wolff,ENGINEER +Ms. Savannah Gaylord,MANAGER +Dejah Jones,MANAGER +Hector Kulas,MANAGER +Graciela Goodwin,MANAGER +Jocelyn Sauer,MANAGER +Miss Lew Hansen,MANAGER +Fannie Fay DDS,ENGINEER +Dr. Jordan Klocko,ENGINEER +Kathlyn Lynch,MANAGER +Leann Botsford,ENGINEER +Ervin Larson,MANAGER +Allie Von,MANAGER +Johanna Kohler III,MANAGER +Hilbert Armstrong,ENGINEER +Tanner Balistreri IV,MANAGER +Abagail Shields,ENGINEER +Gia Cremin,ENGINEER +Mrs. Buford Oberbrunner,ENGINEER +Madelyn White,MANAGER +Abdullah Effertz,MANAGER +Reva Stark,ENGINEER +Camryn McKenzie,MANAGER +Juwan Pouros,MANAGER +Gene Cassin,MANAGER +Felicia Kunde,ENGINEER +Jeremie Anderson,MANAGER +Katheryn Hickle Jr.,MANAGER +Edwina Hamill IV,MANAGER +Adriana Cassin DVM,MANAGER +Nelda Rowe,ENGINEER +Rodrigo Kulas V,ENGINEER +Jaiden Williamson,MANAGER +Cristopher Williamson,MANAGER +Mr. Jay Krajcik,ENGINEER +Francesco Miller,MANAGER +Brenna Reinger,MANAGER +Mr. Mollie Stanton,MANAGER +Coby Schowalter,ENGINEER +Estefania Armstrong II,MANAGER +Aimee Nienow,ENGINEER +Kimberly Batz,ENGINEER +Miss Sienna Pfannerstill,ENGINEER +Johnathon Hammes,ENGINEER +Julien Hansen,MANAGER +Mrs. Emerson Waelchi,MANAGER +Malcolm Streich,MANAGER +Aurelio Lebsack,ENGINEER +Juana Grady,ENGINEER +Kiel Lakin,MANAGER +Sarai Keeling,ENGINEER +Emilia Crona,ENGINEER +Georgianna Kris,MANAGER +Maida Heller,MANAGER +Jena Feeney,ENGINEER +Mabelle Keeling,MANAGER +Chris Bergstrom,ENGINEER +Audrey Block DDS,ENGINEER +Louvenia Kuhn,ENGINEER +Thomas O'Keefe,MANAGER +Darby Klocko,MANAGER +Arlene Weimann,MANAGER +Corbin Jones MD,ENGINEER +Lamar Mraz,ENGINEER +Miss Onie Krajcik,MANAGER +Kamille Schaefer,MANAGER +Jack Borer,ENGINEER +Reese Heaney,MANAGER +Ilene Kovacek,ENGINEER +Trace Bailey,ENGINEER +Wava Donnelly,ENGINEER +Mona Lakin,MANAGER +Weldon Heaney,MANAGER +Norris Labadie,ENGINEER +Bridgette Brown,MANAGER +Osborne Kertzmann,MANAGER +Verlie Bruen,ENGINEER +Enrique Ullrich,ENGINEER +Dr. Asia Purdy,ENGINEER +Lindsey Predovic DDS,MANAGER +Maxine Mosciski,MANAGER +Sydni Stoltenberg,ENGINEER +Paige Buckridge,ENGINEER +Miss Laverne Dach,MANAGER +Murl Abshire,MANAGER +Lou Friesen,MANAGER +Keenan Fahey,ENGINEER +Ashleigh Schultz,ENGINEER +Mrs. Keshaun Lesch,ENGINEER +Jeffrey Langosh,ENGINEER +Mckenzie Boyle,ENGINEER +Hipolito Price PhD,MANAGER +Lesley Adams III,ENGINEER +Mya Howe,ENGINEER +Nick Kutch Sr.,MANAGER +Ms. Winfield Wilkinson,ENGINEER +Leopold Schulist,ENGINEER +Orval Prosacco,ENGINEER +Wilmer Mueller,MANAGER +Karina Batz,MANAGER +Luigi Abbott,MANAGER +Pamela Miller,MANAGER +Emelie Marquardt,ENGINEER +Zola Beier,MANAGER +Mr. Elva Ritchie,ENGINEER +Mrs. Otis Quitzon,MANAGER +Dr. Willow Jacobs,MANAGER +Cathryn Koss,MANAGER +Ms. Alivia Ernser,MANAGER +Timothy Mohr,ENGINEER +Mrs. Jaylan Wuckert,MANAGER +Emerald Waelchi,ENGINEER +Vernon Heathcote,MANAGER +Lavinia Ruecker,ENGINEER +Mr. Quinn Altenwerth,MANAGER +Alejandra Marks,MANAGER +Mr. Leo Wuckert,MANAGER +Jayce Schiller MD,MANAGER +Elenora Ebert Jr.,ENGINEER +Dr. Rose Wyman,MANAGER +Wade Orn,MANAGER +Iva Marks Sr.,MANAGER +Margaret Pouros,MANAGER +Barton Deckow,MANAGER +Miss Lesly Balistreri,ENGINEER +Mr. Jacquelyn Reynolds,ENGINEER +Doyle Heidenreich,MANAGER +Heidi Ruecker,ENGINEER +Mr. Alvis Moen,MANAGER +Dr. Garnet Brown,ENGINEER +Yolanda Beier,ENGINEER +Soledad Macejkovic,ENGINEER +Urban Lowe,ENGINEER +Devyn Schmidt,MANAGER +Barbara Flatley,MANAGER +Patsy Sanford PhD,ENGINEER +Mrs. Rubye Blanda,ENGINEER +Caleigh Klocko,ENGINEER +Kali Dietrich,MANAGER +Ms. Weldon Hudson,ENGINEER +Vallie Huel,ENGINEER +Sven O'Keefe III,ENGINEER +Gerardo Wehner Sr.,ENGINEER +Kyle Kirlin,MANAGER +Marianne Berge Jr.,ENGINEER +Ms. Cristal Connelly,MANAGER +Kailey Spinka,MANAGER +Jeremie Morar,MANAGER +Daija Lind,ENGINEER +Arvel McDermott,ENGINEER +Dr. Nicholas Gorczany,MANAGER +Anne Leuschke,MANAGER +Gerda Cronin,ENGINEER +Ms. Coty Rolfson,ENGINEER +Kareem Gerhold,ENGINEER +Mrs. Nico Mann,ENGINEER +Corbin Bartell,ENGINEER +Theresa Gulgowski,MANAGER +Carmelo Boyer,MANAGER +Elinore Schulist III,MANAGER +Katarina Schultz,MANAGER +Deven Rodriguez II,MANAGER +Miss Bruce Friesen,MANAGER +Marcelle Schowalter,MANAGER +Albertha Murphy PhD,MANAGER +Elmore Doyle,ENGINEER +Reymundo Jaskolski,ENGINEER +Stephania Swaniawski I,MANAGER +Stewart Veum,MANAGER Nathanael Bartell,MANAGER -Johann Nikolaus,MANAGER -Pasquale Wuckert Sr.,ENGINEER -Dr. Itzel Hane,ENGINEER -Carol Rosenbaum,ENGINEER -Marjory Feil,ENGINEER -Arvilla Prohaska,MANAGER -Lester Huel,ENGINEER -Chad Pacocha,ENGINEER -Joseph Brakus,ENGINEER -Aaron Turcotte,MANAGER -Mrs. Madaline Doyle,ENGINEER -Brennan Bogan,MANAGER -Ms. Minerva Homenick,MANAGER -Amie Turner,ENGINEER -Sheila Romaguera,MANAGER -Lazaro Fritsch,MANAGER -Dr. Myrtice Kuhic,ENGINEER -Walter Gusikowski,ENGINEER -Carmel Lehner,ENGINEER -Roxanne Smitham,MANAGER -Emilia Heidenreich DDS,MANAGER -Cyrus Willms III,ENGINEER -Amos Wilderman,ENGINEER -Bettie Will,ENGINEER -Daniella Dickinson,ENGINEER -Judson Gutkowski II,ENGINEER -Otho Greenholt,ENGINEER -Ari Wuckert PhD,ENGINEER -Reese Aufderhar,ENGINEER -Jovani Nolan III,ENGINEER -Winona Willms Sr.,MANAGER -Ms. Tierra Wyman,MANAGER -Melissa Treutel,MANAGER -Celestine Langworth,MANAGER -Everardo Beier,MANAGER -Therese Goldner MD,MANAGER -Miss Cydney Kerluke,MANAGER -Price Bernier,MANAGER -Annetta Hane,ENGINEER -Darrell Mertz,ENGINEER -Lela Labadie PhD,ENGINEER -Olin Nikolaus,MANAGER -Dr. Caterina Runolfsson,ENGINEER -Matt Beier,ENGINEER -William Hessel,ENGINEER -Claudia Cole,MANAGER -Mr. Chet Smith,MANAGER -Dayne Greenholt,MANAGER -Serenity Carroll IV,ENGINEER -Emile Dooley,MANAGER -Bonnie Heathcote,MANAGER -Sheila Brown,ENGINEER -Alisha Greenholt,MANAGER -Cindy Wintheiser Sr.,ENGINEER -Otis Mueller,MANAGER -Gerson Hyatt,MANAGER -Mrs. Jovan Aufderhar,MANAGER -Alexys Torp,MANAGER -Lucius Stanton,MANAGER -Colton DuBuque,ENGINEER -Ms. Annamarie Medhurst,MANAGER -Bryana Grady III,MANAGER -Ms. Wayne Mosciski,ENGINEER -Julio Kuphal IV,MANAGER -Alize Kshlerin,MANAGER -Mrs. Alexandria Harvey,ENGINEER -Aileen Kerluke,ENGINEER -Bria Howell,MANAGER -Aida Stanton IV,ENGINEER -Greta Hoppe,MANAGER -Garrett VonRueden,ENGINEER -Tressa Sipes,MANAGER -Gay Kutch,ENGINEER -Rachelle Marks,MANAGER -Tyreek Weber,MANAGER -Donnell Sauer I,MANAGER -Mr. Ed Russel,MANAGER -Darien Wolf,ENGINEER -Sanford Marks,MANAGER -Erica Cole,ENGINEER -Fanny Shields,MANAGER -Mortimer Nicolas,MANAGER -Reid Daniel,ENGINEER -Joseph Bechtelar,ENGINEER -Matilde Corkery,MANAGER -Luisa White,ENGINEER -Irving Hettinger,MANAGER -Molly Ziemann,ENGINEER -Garrett Renner,ENGINEER -Cathryn Conroy,ENGINEER -Crystel Reichert PhD,MANAGER -Reese Mraz,ENGINEER -Fiona Lakin,MANAGER -Miss Finn Koelpin,ENGINEER -Genevieve Murray V,MANAGER -Newell Leffler,MANAGER -Isabel Ankunding,MANAGER -Malcolm Dickinson,MANAGER -Celestine Greenfelder,MANAGER -Dixie Pouros,MANAGER -Mrs. Hipolito Senger,MANAGER -Narciso Kulas,MANAGER -Lera Leannon,ENGINEER -Reyna Douglas,MANAGER -Ari Rau,MANAGER -Maximo Murphy,MANAGER -Monserrate Ankunding,ENGINEER -Vaughn Emmerich V,ENGINEER -Dena Wiza,ENGINEER -Ara Boyer,ENGINEER -Leanna Bauch,MANAGER -Mr. Marcella Daugherty,ENGINEER -Ms. Billie Smith,MANAGER -Humberto Maggio,MANAGER -Jarrell Roberts,MANAGER -Ronaldo Hauck,MANAGER -Delbert Corwin,ENGINEER -Mrs. Stewart Hammes,ENGINEER -Rudolph Deckow DDS,ENGINEER -Carmine Fritsch,MANAGER -Hannah Mayer DVM,ENGINEER -Merle Boyle,MANAGER -Price Hand,MANAGER -Miss Rhea Mitchell,ENGINEER -Clarissa Pacocha,MANAGER -Mrs. Loma Witting,MANAGER -Johnny Dickens DDS,ENGINEER -Miss Althea Carter,ENGINEER -Elizabeth Trantow,MANAGER -Everett Lind,MANAGER -Esta Smith I,ENGINEER -Clemmie Quitzon,ENGINEER -Lilian Mitchell,ENGINEER -Mac Schulist I,ENGINEER -Brooke Ward,MANAGER -Elaina Beier,MANAGER -Kaleb Larson,ENGINEER -Caden Sporer,ENGINEER -Candice Runte,MANAGER -Leonie Pollich,MANAGER -Mrs. Vivian Ebert,MANAGER -Ewell Wolf,MANAGER -Monty Davis,MANAGER -Jeremie Towne,MANAGER -Estefania Ziemann,MANAGER -Mr. Lon Daniel,MANAGER -Meagan Durgan,ENGINEER -Jules Beahan,ENGINEER -Mr. Hassan Hegmann,ENGINEER -Albina Morissette,MANAGER -Saige Cummings,ENGINEER -Corbin Rempel,MANAGER -Mr. Dario Pfeffer,MANAGER -Mrs. Rodrigo Grady,ENGINEER -Delmer Parker,MANAGER -Camryn Rath,MANAGER -Kirsten Koss,MANAGER -Kaylie Hayes,ENGINEER -Cornell Ebert,MANAGER -Reva Huels,MANAGER -Moses Raynor,ENGINEER -Willard Gottlieb,ENGINEER -Alexys Kerluke,ENGINEER -Desiree Streich,MANAGER -Ms. Gayle Prohaska,ENGINEER -Freddie Mayer,ENGINEER -Corrine Larson,MANAGER -Emilio Jast,ENGINEER -Mrs. Bobby Bernier,MANAGER -Johnson Beahan,MANAGER -Jewel Jacobi PhD,MANAGER -Dr. Lori Hagenes,ENGINEER -Dr. Austyn Streich,MANAGER -Alex Greenholt,MANAGER -Ida Lang,ENGINEER -Uriel Powlowski,MANAGER -Domenic Ratke,MANAGER -Herminio Schamberger MD,ENGINEER -Alexa Shanahan Sr.,ENGINEER -Kasandra Brekke,MANAGER -Maida Marquardt,MANAGER -Florian McGlynn,ENGINEER -Hilton Jast,ENGINEER -Miss Maryse Sporer,ENGINEER -Zoie Gulgowski,ENGINEER -Minnie Bernier,ENGINEER -Mabelle Pouros Jr.,ENGINEER -Miss Axel Dach,MANAGER -Golda Pfannerstill PhD,ENGINEER -Tristin Goldner,MANAGER -Dr. Jaunita Schinner,MANAGER -Miss Leonel Shields,MANAGER -Mrs. Kirstin Daugherty,MANAGER -Aleen Quigley MD,MANAGER -Krystina Beatty PhD,ENGINEER -Alanis Keebler,MANAGER -Monica Botsford,ENGINEER -Roselyn Deckow,MANAGER -Mrs. Lucy McGlynn,MANAGER -Rey Gibson,ENGINEER -Shaylee Jerde,ENGINEER -Ethyl Fay,MANAGER -Dulce Fay,MANAGER -Dayana Goyette,ENGINEER -Kurt Lesch Sr.,ENGINEER -Alejandra Senger,MANAGER -Broderick Jakubowski,ENGINEER -Neva Hartmann,ENGINEER -Tamia Ziemann,ENGINEER -Maida Hermiston DVM,MANAGER -Paige Rempel,ENGINEER -Sherman Torphy,MANAGER -Doug Pfannerstill,MANAGER -Mr. Alicia Rice,ENGINEER -Justine Sawayn,MANAGER -Camren Kerluke,ENGINEER -Kitty Trantow,MANAGER -Baby Barrows,MANAGER -Bridie Haley,MANAGER -Kyra Kemmer,ENGINEER -Khalil Blanda,MANAGER -Madalyn Kassulke,MANAGER -Ebba Gislason I,ENGINEER -Gloria Skiles,MANAGER -Gardner Conroy,MANAGER -Wilfrid Gottlieb,ENGINEER -Roslyn Labadie V,ENGINEER -Eliseo Considine DDS,ENGINEER -Lowell Keebler,ENGINEER -Hailee Kuhn,MANAGER -Cordia Bayer,MANAGER -Harry Koch,MANAGER -Sarai Rodriguez PhD,MANAGER -Mrs. Elnora Kutch,MANAGER -Ms. Geovany Toy,MANAGER -Zoe Jacobs,MANAGER -D'angelo Ratke,ENGINEER -Kenna Purdy,MANAGER -Cale Prohaska,MANAGER -Shania Conn,ENGINEER -Miss Daphnee Schuster,ENGINEER -Buford Koelpin,MANAGER -Erick Jaskolski,MANAGER -Briana Kunze MD,ENGINEER -Rahsaan Hodkiewicz,ENGINEER -Dr. Izabella Nolan,ENGINEER -Ms. Lavinia Wuckert,ENGINEER -Ms. Theron Grimes,ENGINEER -Christina Mertz,ENGINEER -Mr. Toby Powlowski,MANAGER -Ms. Yolanda Spinka,MANAGER -Ms. Dayne McCullough,MANAGER -Patsy Legros,MANAGER -Carley Berge PhD,MANAGER -Jairo Champlin,MANAGER -Elvie Schiller,ENGINEER -Delta Bayer V,MANAGER -Dr. Russ Treutel,ENGINEER -Gerry Bartell,ENGINEER -Lennie Trantow,MANAGER -Orland Streich,ENGINEER -Alberta Considine,MANAGER -Ms. Leda Rowe,ENGINEER -Mrs. Gregory Hettinger,ENGINEER -Elinore Grant,MANAGER -Buddy Nader,ENGINEER -Violette Stanton Sr.,MANAGER -Hilton Daniel,ENGINEER -Ms. Julia Eichmann,ENGINEER -Earline McCullough,MANAGER -Juston Turner III,ENGINEER -Vivien Volkman,ENGINEER -Mina Streich MD,ENGINEER -Timmothy Lowe II,MANAGER -Dashawn Bechtelar I,MANAGER -Viola Parisian,MANAGER -Johnson Grady,MANAGER -Marcos Huel,MANAGER -Donna Crona,ENGINEER -Bella Gibson,ENGINEER -Broderick Dickens,MANAGER -Bobbie Heathcote,MANAGER -Lauretta McClure DDS,ENGINEER -Dortha Grimes Jr.,ENGINEER -Annamae Gislason,MANAGER -Carli Friesen,ENGINEER -Ms. Rupert Waters,MANAGER -Americo Walsh DDS,MANAGER -Melyssa Pacocha,ENGINEER -Mr. Deshaun Swift,MANAGER -Frank Wolf,MANAGER -Claudine Bruen,ENGINEER -Jett Zemlak,ENGINEER -Ms. Ruth Weber,ENGINEER -Adella Hammes,MANAGER -Harmon Conn,MANAGER -Jude Durgan,ENGINEER -Frank Veum I,ENGINEER -Keon Berge,ENGINEER -Ashleigh McGlynn,ENGINEER -Peyton Heidenreich,MANAGER -Mr. Filomena Satterfield,ENGINEER -Jeffrey Hessel,ENGINEER -Mr. Libbie Denesik,MANAGER -Cole Leffler I,ENGINEER -Haskell Greenfelder IV,MANAGER -Carmela Koss,ENGINEER -Omer Larson,ENGINEER -Earlene Smith,ENGINEER -Lisandro Howell II,MANAGER -Nash Lakin,MANAGER -Torrance Bode,ENGINEER -Gene Emmerich,MANAGER -Jayden Lueilwitz,ENGINEER -Mable Satterfield,ENGINEER -Braulio Hodkiewicz,MANAGER -Scotty Reilly,MANAGER -Diana Kautzer Sr.,MANAGER -Cyrus Keeling,ENGINEER -Mrs. Alexie Upton,ENGINEER -Jamarcus Kihn,MANAGER -Hudson Stokes,MANAGER -Danyka Gulgowski,ENGINEER -Kiara Rohan,MANAGER -Mr. Asia Price,ENGINEER -Danial Walsh Jr.,ENGINEER -Mr. Mariana Boyer,MANAGER -Matilde Bechtelar,MANAGER -Sammy Thompson,MANAGER -Chris Leannon,ENGINEER -Verda Hahn,MANAGER -Jarod McGlynn,MANAGER -Hattie Halvorson,MANAGER -Myron Aufderhar,MANAGER -Merlin Koelpin,MANAGER -Mr. Cade Farrell,MANAGER -Allan Collier,ENGINEER -Mrs. Stella Labadie,ENGINEER -Ettie Zulauf,MANAGER -Annabel Towne,ENGINEER -Megane DuBuque III,MANAGER -Jerrold Gorczany,MANAGER -Helena Flatley,MANAGER -Norma Kautzer II,ENGINEER -Thurman Powlowski,MANAGER -Dee Herzog,ENGINEER -Olaf Strosin,ENGINEER -Mrs. Imogene Kunde,ENGINEER -Miss Merl Hauck,MANAGER -Miss Haskell Halvorson,MANAGER -Mr. Margaret Johns,ENGINEER -Katelyn Huels,MANAGER -Else Swaniawski,MANAGER -Gwen Kessler,MANAGER -Santino Raynor,ENGINEER -Mrs. Alene Zieme,MANAGER -Amira Hermiston,MANAGER -Hilbert Lindgren,MANAGER -Nayeli Rolfson,MANAGER -Rosalee Kovacek,ENGINEER -Ashton Weissnat,ENGINEER -Sonya Skiles,MANAGER -Mr. Clarabelle Heaney,MANAGER -Polly McClure I,ENGINEER -Joaquin O'Conner,MANAGER -Lionel Moen,MANAGER -Lavina Farrell PhD,MANAGER -Mr. Marcel Jacobs,ENGINEER -Freeda Beer,MANAGER -Karley Considine Sr.,ENGINEER -Mrs. Benedict Hills,ENGINEER -Maverick Kris,ENGINEER -Jana McDermott,ENGINEER -Lewis Quitzon,MANAGER -Jazlyn Littel,MANAGER -Serena Pfannerstill,MANAGER -Dr. Christiana Rodriguez,ENGINEER -Crystal Hintz,ENGINEER -Antonette Kiehn,MANAGER -Vena Osinski PhD,ENGINEER -Herta Gorczany,ENGINEER -Carol Quigley,MANAGER -Blanche Gottlieb,MANAGER -Ms. Kellie Ledner,ENGINEER -Rae Daniel,MANAGER -Elizabeth Borer,ENGINEER -Nelle Botsford,ENGINEER -Jimmie Roob PhD,ENGINEER -Delia Erdman V,MANAGER -Cyril Brekke,MANAGER -Miss Alphonso Bailey,MANAGER -Suzanne Treutel,ENGINEER -Alayna Krajcik,ENGINEER -Roberta Lind,MANAGER -Ryleigh Dicki,MANAGER -Mrs. Adolph Carroll,ENGINEER -Elena Emard,MANAGER -Meggie Bechtelar,ENGINEER -Jan Rolfson,ENGINEER -Greg Roberts,ENGINEER -Alison Labadie,MANAGER -Cielo Dietrich,MANAGER -Erika Dibbert,ENGINEER -Michale Bailey Sr.,MANAGER -Rene Bahringer,MANAGER -Joyce Hyatt DVM,ENGINEER -Turner Windler,ENGINEER -Camille Ullrich,ENGINEER -Faye Kemmer,MANAGER -Albertha Crona,MANAGER -Miss Vicente Schowalter,ENGINEER -Bridgette Fay,ENGINEER -Javier Mayer,MANAGER -America O'Keefe,MANAGER -Reese Swift,MANAGER -Miss Susan Tromp,ENGINEER -Cathy Berge,ENGINEER -Freddie Kuhic,ENGINEER -Vivian Wolff,ENGINEER -Rosalee Nienow,ENGINEER -Marcelina Wyman,MANAGER -Carmen Jakubowski,ENGINEER -Patience Kuhn,ENGINEER -Ross O'Reilly,MANAGER -Carol Schmitt,ENGINEER -Ms. Gracie Durgan,ENGINEER -Annabelle McGlynn,ENGINEER -Mrs. Saul Carter,MANAGER -Kayla Leuschke,MANAGER -Okey Wilderman PhD,MANAGER -Rodrigo Kovacek,MANAGER -Ms. Arnoldo Lehner,ENGINEER -Ms. Alek Rippin,MANAGER -Keara Kemmer III,ENGINEER -Garry Mertz,MANAGER -Aliyah Jacobson,MANAGER -Mrs. Lauriane Reynolds,MANAGER -Dallin Cormier,MANAGER -Dr. Ashlee Stehr,ENGINEER -Miss Kendra Tremblay,MANAGER -Clement Cremin,ENGINEER -Nicholas Lakin,MANAGER -Jessyca Bosco,ENGINEER -Richard Emmerich,MANAGER -Mrs. Norwood Rempel,MANAGER -Maritza Osinski,MANAGER -Lorena Barton,MANAGER -Mr. Keara Marvin,ENGINEER -Jeanie Hirthe,MANAGER -Cole Abshire,MANAGER -Gudrun Lind,MANAGER -Skylar Goldner Jr.,MANAGER -Cicero Terry,MANAGER -Paul O'Keefe DVM,ENGINEER -Ferne Schoen,ENGINEER -Keaton Kling,MANAGER -Alicia Bauch,ENGINEER -Jalyn Huels,MANAGER -Wilburn Weissnat,ENGINEER -Simeon Fisher,ENGINEER -Patrick Pouros,ENGINEER -Garrison Roob,ENGINEER -Mariah Mitchell I,MANAGER -Guy Hintz Jr.,MANAGER -Earl Sipes,ENGINEER -Miss Thalia Ratke,ENGINEER -Dr. Leanne Greenfelder,ENGINEER -Blanca Kulas,MANAGER -Fanny Wisozk II,ENGINEER -Flo Ratke,ENGINEER -Mr. Herminio O'Kon,ENGINEER -Emerald Stamm,ENGINEER -Dawn Grimes,MANAGER -Kathryne Hills,ENGINEER -Jordane Thiel,MANAGER -Antonio Hammes I,MANAGER -Zackery McDermott,MANAGER -Shania Schultz PhD,ENGINEER -Genevieve Moore,ENGINEER -Mattie Turner,MANAGER -Caesar Glover I,MANAGER -Mitchell Haley Jr.,ENGINEER -Itzel Wisozk,ENGINEER -Mr. Victoria Mraz,ENGINEER -Verona Bailey,ENGINEER -Lorenza Beahan,ENGINEER -Tre DuBuque,MANAGER -Missouri Walker,MANAGER -Reynold Smitham,MANAGER -Jedediah Bins Jr.,MANAGER -Miss Jacinthe Tillman,MANAGER -Mrs. Imelda Okuneva,MANAGER -Chadd Botsford,MANAGER -Jailyn Sanford,MANAGER -Monte Windler,ENGINEER -Marguerite Kozey,ENGINEER -Dr. Buck Kuhic,MANAGER -Cristian Monahan,MANAGER -Madge Rosenbaum V,MANAGER -Serena Kunze,ENGINEER -Max Mueller,MANAGER -Miss Eda Torp,MANAGER -Mrs. Alivia Harris,ENGINEER -Dr. Haylie Waters,ENGINEER -Maxie Simonis IV,ENGINEER -Dennis Olson DDS,ENGINEER -Jason Lesch,ENGINEER -Marlin Hermiston,MANAGER -Devyn Gaylord Sr.,MANAGER -Jalon Schaden,MANAGER -Dillan Mertz PhD,ENGINEER -Nico Bosco,MANAGER -Abigale Stark,ENGINEER -Ms. Rubye Hahn,ENGINEER -Zoey Lemke II,ENGINEER -Norris Ziemann,MANAGER -Anissa Wintheiser,MANAGER -Janie Halvorson,MANAGER -Geovany Dickinson I,ENGINEER -Rosalind Lebsack,ENGINEER -Ms. Rosalinda Brekke,ENGINEER -Estefania Lowe,ENGINEER -Eda Lind,ENGINEER -Lessie Langworth,MANAGER -Virginie Turcotte,MANAGER -Maud Terry,MANAGER -Bobbie Cartwright,MANAGER -Keaton Crona,ENGINEER -Norris Schumm,MANAGER -Miss Jamey Gusikowski,ENGINEER -Gus Heathcote,ENGINEER -Julio Ward,ENGINEER -Samara Goldner,ENGINEER -Reuben Mohr,ENGINEER -Clinton Howell,MANAGER -Keyshawn Sipes,ENGINEER -Giovani Marks,MANAGER -Norberto Romaguera,MANAGER -Rose Mante,MANAGER -Nathan Stoltenberg,MANAGER -Keira Breitenberg,ENGINEER -Tanner Borer,MANAGER -Ms. Ike Pfeffer,ENGINEER -Dr. Helena Ernser,MANAGER -Sophie Harris,ENGINEER -Mrs. Jessyca Bayer,MANAGER -Aleen Bogisich,MANAGER -Zakary Schmeler,MANAGER -Katelin Luettgen,MANAGER -America Schuppe V,MANAGER -Adalberto VonRueden,ENGINEER -Mohamed Lueilwitz,MANAGER -Luna Lowe,MANAGER -Bradley Marquardt DVM,ENGINEER -Jamie Goldner,MANAGER -Haley Bradtke,ENGINEER -Paris Langworth,MANAGER -Trevor Lubowitz MD,MANAGER -Fred Glover,ENGINEER -Jimmy Jakubowski I,ENGINEER -Asia Runolfsson,MANAGER -Destiny Cassin,MANAGER -Barry Reilly,MANAGER -Amari Mills PhD,ENGINEER -Sandrine Nitzsche,ENGINEER -Rocio Bechtelar DDS,ENGINEER -Ms. Charity MacGyver,MANAGER -Mrs. Mac Robel,MANAGER -Icie Cummerata,MANAGER -Felix Trantow,MANAGER -Dr. Makenzie Wuckert,MANAGER -Melany Donnelly,MANAGER -Tyrell Zulauf,ENGINEER -Boyd Stroman,ENGINEER -Jeramie Botsford,MANAGER -Pascale Stehr,ENGINEER -Savanna Wisoky,ENGINEER -Justus Morar Jr.,ENGINEER -Reymundo Thiel,MANAGER -Dr. Geraldine Bogan,ENGINEER -Ibrahim Treutel V,ENGINEER -Alexandrine Rohan,ENGINEER -Emilio Witting,MANAGER -Brook Lueilwitz,MANAGER -Mr. Madelyn Collins,ENGINEER -Trenton Skiles,ENGINEER -Demario Cremin,MANAGER -Miss Leonel Ullrich,ENGINEER -Nasir Predovic,MANAGER -Darion Zboncak I,MANAGER -Ophelia Spencer,ENGINEER -Deron Marks,MANAGER -Mr. Warren Lemke,ENGINEER -Stephen O'Reilly,MANAGER -Monica Orn,MANAGER -Anahi Graham,ENGINEER -Ocie Carter,MANAGER -Kailey Haag,MANAGER -Rupert Mills,ENGINEER -Tina Braun,MANAGER -Kiel Johns,MANAGER -Frederick DuBuque,ENGINEER -Grace Boyle PhD,MANAGER -Emily Wyman,ENGINEER -Branson Hoeger,MANAGER -Miss Gussie Johnson,MANAGER -Valerie Howell,ENGINEER -Angie Hand,MANAGER -Joesph O'Kon,ENGINEER -Dr. Leatha Greenfelder,ENGINEER -Gregoria Lindgren,MANAGER -Rollin Lindgren PhD,ENGINEER -Randy Ankunding,MANAGER -Jesse Bosco,ENGINEER -Mrs. Amari Schimmel,MANAGER -Haven O'Reilly DVM,ENGINEER -Bradford Swaniawski,ENGINEER -Ms. Jazlyn Runolfsdottir,MANAGER -Enoch Ratke,MANAGER -Madaline Sporer,ENGINEER -Nathen Turcotte,MANAGER -Sheridan Homenick,MANAGER -Dr. Rosalinda Breitenberg,MANAGER -Kali O'Conner PhD,MANAGER -Jaleel Lockman,MANAGER -Miss Demarcus Erdman,MANAGER -Destiny Miller,ENGINEER -Krystina Funk,MANAGER -Ruth Hirthe,MANAGER -Mohammed Boehm,ENGINEER -Michale Farrell Sr.,ENGINEER -Kaya Hermann,ENGINEER -Mabelle Haley,ENGINEER -Ms. Bessie Moen,ENGINEER -Vito Smitham,MANAGER -Carley Rogahn Sr.,ENGINEER -Carli West IV,ENGINEER -Alexie Raynor,ENGINEER -Beth Walsh,ENGINEER -Shanna Witting,MANAGER -Evelyn Zemlak,ENGINEER -Sibyl O'Connell,ENGINEER -Emmanuel Johnson,MANAGER -Sandra Will,ENGINEER -Samanta Quitzon I,MANAGER -Jacky Rosenbaum,ENGINEER -Terrell Rolfson IV,MANAGER -Kathryn Pouros,ENGINEER -Dameon Cummerata,MANAGER -Mr. Tanner O'Kon,ENGINEER -Alex Anderson,MANAGER -Junior Gottlieb,ENGINEER -Tianna Greenholt,MANAGER -Kristy Nicolas,MANAGER -Alanna Baumbach,MANAGER -Christina Greenholt,MANAGER -Cathrine Ferry,ENGINEER -Mr. Hayden Lueilwitz,MANAGER -Rodrigo Stehr,MANAGER -Bobbie Stehr II,ENGINEER -Carleton Kovacek Jr.,ENGINEER -Jett Bruen,MANAGER -Carroll Hegmann MD,MANAGER -Mrs. Newell Maggio,MANAGER -Elyse Quigley,MANAGER -Fabiola Wilkinson,MANAGER -Delilah Schulist,MANAGER -Johnson Stehr,MANAGER -Xavier Kilback DVM,ENGINEER -Anastasia Wisozk,ENGINEER -Pascale Hamill,ENGINEER -Mr. Rocky Quigley,ENGINEER -Lawrence McCullough,ENGINEER -Magali Balistreri,ENGINEER -Annie Roob,MANAGER -Amina Rempel,MANAGER -Marc Gislason,ENGINEER -Norwood Boyle,MANAGER -Monserrat Quitzon,ENGINEER -Mrs. Abbigail Reichel,MANAGER -Isac Pacocha,ENGINEER -Unique Hansen,ENGINEER -Ivy Dietrich DDS,MANAGER -Gavin Kuhlman,MANAGER -Judson Fahey PhD,ENGINEER -Orval Effertz,ENGINEER -Karlie Keeling,MANAGER -Selina McDermott,ENGINEER -Raquel Heller,MANAGER -Blaise Ritchie,ENGINEER -Lynn Mante,ENGINEER -Constance Kemmer,MANAGER -Miss Wilber Yundt,ENGINEER -Dora Reinger,MANAGER -Mose Renner,ENGINEER -Koby Kirlin,ENGINEER -Denis Denesik,MANAGER -Randal Runolfsson,ENGINEER -Adrianna Bauch,ENGINEER -Zackery Adams MD,ENGINEER -Bruce Willms III,MANAGER -Ms. Alexander Jaskolski,ENGINEER -Miss Lera Schuster,ENGINEER -Hyman Herzog II,ENGINEER -Morton Labadie,MANAGER -Allen Hane,MANAGER -Katherine DuBuque Sr.,MANAGER -Lelah Bins,ENGINEER -Ottis Connelly Jr.,MANAGER -Ilene Ebert,ENGINEER -Phoebe Mraz DDS,MANAGER -Ms. Peter Osinski,MANAGER -Dana Strosin,MANAGER -Tiffany Reinger,ENGINEER -Marlen Herman,ENGINEER -Merlin Eichmann,ENGINEER -Mrs. Mateo Schaden,MANAGER -Yasmeen Powlowski,ENGINEER -Shaylee West,MANAGER -Royal Batz PhD,MANAGER -Miss Vesta Stehr,MANAGER -Edgardo Shields,ENGINEER -Sylvan Kohler,ENGINEER -Cordelia Pagac,MANAGER -Dr. Stephany Kling,MANAGER -Ms. Kelsi Wilkinson,MANAGER -Shanna Ebert II,MANAGER -Isobel Keebler,MANAGER -Cynthia Osinski III,ENGINEER -Lavonne Schinner,MANAGER -Josue Bode,ENGINEER -Ms. Fermin Ward,MANAGER -Nichole Casper,ENGINEER -Cameron Kautzer,MANAGER -Jacklyn Hayes MD,ENGINEER -Dayne O'Conner MD,ENGINEER -Miss Trystan Deckow,ENGINEER -Arvel Bode,MANAGER -Mr. Jeffrey Nienow,MANAGER -Dr. Hiram Dickinson,MANAGER -Elsie Reinger,MANAGER -Mustafa Hand,ENGINEER -Schuyler McGlynn,MANAGER -Shayne Tremblay,MANAGER -Levi Donnelly,MANAGER -Miss Queen Lind,ENGINEER -Nadia Koch Jr.,ENGINEER -Jaclyn Kunze,ENGINEER -Cheyenne Johns,ENGINEER -Margot Kutch,ENGINEER -Jimmie Friesen,ENGINEER -Tony O'Reilly,MANAGER -Antone Yundt,ENGINEER -Iliana VonRueden DDS,ENGINEER -Elmo Howe DVM,ENGINEER -Marlon Muller,ENGINEER -Aliyah Mitchell,ENGINEER -Jackson Carter,ENGINEER -Brendan Bergnaum V,MANAGER -Dallin Mueller,ENGINEER -Enid Hodkiewicz,ENGINEER -Edmund Cole,MANAGER -Isabella Miller,ENGINEER -Maddison Raynor Sr.,ENGINEER -Jasper Stoltenberg,MANAGER -Herminio Rippin,MANAGER -Mr. Laverne Hartmann,MANAGER -Leonel Daugherty,MANAGER -Miss Gabrielle Gislason,MANAGER -Michele Quigley,ENGINEER -Jennie Greenfelder,MANAGER -Dana Glover,MANAGER -Keshaun Lakin,ENGINEER -Martin Collier,ENGINEER -Dewayne Nienow,ENGINEER -Miss Carlotta Gorczany,ENGINEER -Rickie Jones,ENGINEER -Reanna Swift,MANAGER -Maxie Johnston,MANAGER -Isac Kuphal,MANAGER -Miss Harvey Reichert,ENGINEER -Isobel Hauck Jr.,ENGINEER -Leila Moen I,MANAGER -Art Hirthe,ENGINEER -Roxanne Ondricka,MANAGER -Anais Kiehn,ENGINEER -Elvie Hodkiewicz,ENGINEER -Kailyn Dietrich,ENGINEER -Mr. Hershel Bernier,ENGINEER -Paolo Goodwin DVM,MANAGER -Geovany Lehner,ENGINEER -Terry Raynor,MANAGER -Audreanne Walker,ENGINEER -Breanne Ziemann,MANAGER -Brenden Altenwerth,ENGINEER -Gilberto Paucek,MANAGER -Wilson Jerde,ENGINEER -Matilde Sporer III,ENGINEER -Fredrick Thompson,ENGINEER -Krista Emard,ENGINEER -Frida Quitzon,ENGINEER -Chanel Romaguera,ENGINEER -Leon Larkin,MANAGER -Tillman Thompson,ENGINEER -Branson Stiedemann,ENGINEER -Ophelia Wolff,MANAGER -Jaeden Purdy,MANAGER -Daryl Sanford DDS,ENGINEER -Blair Hamill,MANAGER -Geo Gulgowski,ENGINEER -Mathew Von IV,ENGINEER -Kyleigh Hane,ENGINEER -Ms. Reece Thompson,MANAGER -Darryl Hagenes,ENGINEER -Vivian Hudson,ENGINEER -Norris Blanda PhD,ENGINEER -Thomas Crist,ENGINEER -Mr. Bernadette Hackett,MANAGER -Grace Jacobi,ENGINEER -Josefina Lynch,ENGINEER -Fidel Hahn,ENGINEER -Mrs. Declan Bechtelar,MANAGER -Yadira Deckow,ENGINEER -Neal Greenholt,ENGINEER -Norwood Wehner,ENGINEER -Miss Marisa Kirlin,MANAGER -Mr. Clovis Steuber,ENGINEER -Janiya Hartmann,MANAGER -Delaney Walsh,ENGINEER -Alycia Homenick,ENGINEER -Saul Daugherty PhD,MANAGER -Cecil Durgan,ENGINEER -Cornell Pollich,ENGINEER -Seamus Hermann,MANAGER -Ms. Lempi Hickle,ENGINEER -Mr. Willis Mertz,MANAGER -Geraldine Mertz,ENGINEER -Clyde Williamson,ENGINEER -Alvera Schiller II,ENGINEER -Evalyn Buckridge,ENGINEER -Chauncey Schinner,MANAGER -Colleen Ortiz,ENGINEER -Layla Ernser,ENGINEER -Shanie Gutkowski,MANAGER -Guido Rau,MANAGER -Ms. Fausto Mante,ENGINEER -Jayne Smitham Sr.,MANAGER -Ms. Pattie Marks,ENGINEER -Dock Kozey,ENGINEER -Nelle Crona,MANAGER -Pamela O'Reilly,MANAGER -Glenna Cronin,MANAGER -Rosendo Morissette,ENGINEER -Vesta Johnson,MANAGER -Ms. Don Lynch,ENGINEER -Brady Jacobson,ENGINEER -Monty Kulas,MANAGER -Jettie Adams IV,ENGINEER -Demetris Rutherford,MANAGER -Lucious Crooks,ENGINEER -Dawn Renner IV,ENGINEER -Roger Skiles,MANAGER -Twila Erdman,MANAGER -Gregory Becker,MANAGER -Delaney Huels,ENGINEER -Hillary Braun,ENGINEER -Juston Lesch,MANAGER -Rowan Nikolaus,ENGINEER -Alva Blanda,MANAGER -Mr. Bria Marvin,MANAGER -Ashlee Kiehn,ENGINEER -Janiya Waelchi,MANAGER -Jacey Rohan,MANAGER -Hilma Dickinson,ENGINEER -Leanne Reichert II,ENGINEER -Ilene Johns,ENGINEER -Cyril Thiel,ENGINEER -Tina Tillman DVM,ENGINEER -Hobart Bechtelar,ENGINEER -Lemuel Lemke,MANAGER -Niko Rolfson,MANAGER -Stephanie Windler,MANAGER -Foster Torphy,MANAGER -Laverne MacGyver,MANAGER -Riley Schowalter,MANAGER -Constance Welch,MANAGER -Julius Reinger,MANAGER -Lourdes Grimes,ENGINEER +Retha Rempel,ENGINEER +Isidro Aufderhar,MANAGER +Florencio Mohr,MANAGER +Zella Weimann,ENGINEER +Khalid Macejkovic,ENGINEER +Geraldine Torp,ENGINEER +Presley Marks,ENGINEER +Mrs. Eve Bartoletti,ENGINEER +Corine Schimmel,MANAGER +Citlalli Goldner DDS,MANAGER +Zakary Botsford,MANAGER +Florida Reilly,ENGINEER +Mr. Patsy Doyle,MANAGER +Emily Hayes II,ENGINEER +Dr. Johann Turcotte,ENGINEER +Dr. Darion Dietrich,ENGINEER +Norris Brekke,MANAGER +Janessa Marquardt,MANAGER +Felicita Wintheiser MD,MANAGER +Melyssa Muller,ENGINEER +Vivienne Weissnat DDS,ENGINEER +Ford Gerlach,ENGINEER +Keenan Kertzmann,ENGINEER +Tobin Goyette,ENGINEER +Cecilia Green,MANAGER +Abe Fisher,MANAGER +Mrs. Annabell Morissette,MANAGER +Danny Kautzer III,MANAGER +Gerard Cruickshank,ENGINEER +Joy Okuneva DDS,MANAGER +Ezequiel Macejkovic,ENGINEER +Vernie Bradtke IV,ENGINEER +Trycia Muller,ENGINEER +Clotilde Ankunding,ENGINEER +Mr. Chaya Abshire,ENGINEER +Hanna Roob,MANAGER +Ronny Dietrich,ENGINEER +Derek Durgan DVM,MANAGER +Demetrius West,MANAGER +Macey Nikolaus,MANAGER +Edison Gottlieb III,MANAGER +Bo Collins,ENGINEER +Michaela Pagac PhD,MANAGER +Mireille Kunde I,ENGINEER +Duncan Kulas,MANAGER +Mrs. Xzavier Smitham,ENGINEER +Mr. Adrianna Baumbach,MANAGER +Shad Rolfson,MANAGER +Mr. Dimitri Baumbach,MANAGER +Samara Schultz,MANAGER +Mrs. Brant Kautzer,MANAGER +Tierra Greenholt,ENGINEER +Otho Kub,MANAGER +Ana Harber,MANAGER +Ted Mertz Sr.,ENGINEER +Uriel Zieme,MANAGER +Mr. Adaline Wolff,ENGINEER +Mrs. Maurice Senger,MANAGER +Ada Gleason,MANAGER +Edwina Bernier DVM,ENGINEER +Elva Homenick,ENGINEER +Mrs. Shane Powlowski,MANAGER +Obie Nikolaus,MANAGER +Ottis Jakubowski,MANAGER +Mr. Armand Leannon,ENGINEER +Jaime Kuvalis,ENGINEER +Loyce VonRueden Jr.,ENGINEER +Evangeline Johns,MANAGER +Federico Halvorson,MANAGER +Sylvester Gerlach,MANAGER +Ashly Wunsch V,MANAGER +Rowland Miller,MANAGER +Bryon Kunde,MANAGER +Denis Ernser,ENGINEER +Jovanny O'Reilly DVM,ENGINEER +Lazaro Hermiston,MANAGER +Kelly Stehr,ENGINEER +Oda Fadel,ENGINEER +Percival Armstrong,ENGINEER +Caleigh Schimmel,MANAGER +Tyrique Pfeffer,ENGINEER +Adell Leuschke,ENGINEER +Dr. Cade Farrell,ENGINEER +Gisselle Doyle,MANAGER +Lily Reinger,MANAGER +Jeffrey Gleason,ENGINEER +Tad Huel,ENGINEER +Connor Conn,MANAGER +Mr. Cathrine Casper,ENGINEER +Dr. Beryl Rempel,MANAGER +Carlie Steuber,MANAGER +Rose Frami IV,ENGINEER +Mr. Tyrel Pagac,MANAGER +Morton Trantow,ENGINEER +Lola Ortiz,MANAGER +Kelton Champlin,MANAGER +Owen Mayert,ENGINEER +Johnny Witting,MANAGER +Thea Rolfson,MANAGER +Reanna Schmidt,ENGINEER +Ian Stehr,MANAGER +Mr. Patsy Purdy,MANAGER +Brady Ritchie,ENGINEER +Thea Effertz,MANAGER +Gerry Veum,ENGINEER +Corene Adams,MANAGER +Julian Kutch,MANAGER +Mrs. Joe Connelly,ENGINEER +Adolphus Paucek,MANAGER +Jasmin Ledner II,ENGINEER +Dr. Osbaldo Beatty,ENGINEER +Dr. Mckenna Haag,MANAGER +Abdiel Connelly DVM,ENGINEER +Liliana Baumbach III,MANAGER +Willard Kuvalis V,ENGINEER +Carolyn Jaskolski Jr.,MANAGER +Reta Franecki,ENGINEER +Percival O'Kon,ENGINEER +Kamryn Rath,MANAGER +Hailey Dooley,MANAGER +Mrs. Brycen West,ENGINEER +Margarette Miller,MANAGER +Cristian Pagac,MANAGER +Rosalee Bechtelar,MANAGER +Lessie Lesch,MANAGER +Iva Hegmann,MANAGER +Hallie Schroeder,MANAGER +Mr. Lola Volkman,ENGINEER +Arianna Wolf DDS,MANAGER +Elliot Trantow,MANAGER +Darrion Rath PhD,ENGINEER +Coralie Effertz V,ENGINEER +Ms. Corrine Effertz,ENGINEER +Ellie Keebler,ENGINEER +Tyrese Pfeffer,ENGINEER +Jayce Roberts,MANAGER +Isobel Veum,ENGINEER +Raphaelle Breitenberg,ENGINEER +Maudie Labadie I,ENGINEER +Rosario Langosh MD,ENGINEER +Raheem Mohr,MANAGER +Avery Lind,ENGINEER +Nichole Waters,ENGINEER +Blaise Gislason I,MANAGER +Everette D'Amore,ENGINEER +Darwin Conroy,MANAGER +Abdullah Heathcote,ENGINEER +Burnice Treutel,MANAGER +Libbie O'Hara,ENGINEER +Rowan Will,MANAGER +Gudrun Gleason,ENGINEER +Fannie Quitzon,MANAGER +Terence Gutkowski,ENGINEER +Tyshawn Rowe,MANAGER +Jaylan Sanford,MANAGER +Camille Schaden DVM,MANAGER +Ms. Blaze Emmerich,MANAGER +Sonny Stoltenberg,ENGINEER +Elsie Jacobson,MANAGER +London Jacobs,MANAGER +Mr. Hassie Kuhn,ENGINEER +Raul Bogan MD,ENGINEER +Adrian Abshire,MANAGER +Golden Kreiger,MANAGER +Deven Stiedemann I,MANAGER +Hilario Koepp PhD,MANAGER +Maynard Herzog,MANAGER +Nathaniel Torp,MANAGER +Courtney Strosin,MANAGER +Emely Lowe,ENGINEER +Vilma Weber,ENGINEER +Ms. Carlee Littel,ENGINEER +Hayden Mills,ENGINEER +Ervin Schimmel,ENGINEER +Gino Ortiz,ENGINEER +Amani Conroy,MANAGER +Korbin Lowe,MANAGER +Turner Bogan I,ENGINEER +Ms. Jabari Bauch,MANAGER +Mrs. Breanne Morissette,ENGINEER +Crystel Doyle,MANAGER +Isabel VonRueden,ENGINEER +Dayne Cremin,ENGINEER +Waino Armstrong,MANAGER +Deborah Armstrong,MANAGER +Ashlynn Mante DVM,ENGINEER +Karlie Pollich Jr.,ENGINEER +Maeve Schroeder,MANAGER +Hanna Fadel,ENGINEER +Delphia O'Hara,MANAGER +Jamir Hammes,ENGINEER +Nigel Ortiz,ENGINEER +Pauline Ritchie,MANAGER +Nicholaus Toy,ENGINEER +Freddy Okuneva,MANAGER +Brionna Fritsch,ENGINEER +Maiya Mills,ENGINEER +Alia Hoeger PhD,MANAGER +Alvina Mertz,ENGINEER +Raymundo Hintz,ENGINEER +Zack Stamm,ENGINEER +Dayne Klocko,MANAGER +Kyla Cremin,ENGINEER +Izabella Bernhard,ENGINEER +Zena Yundt,ENGINEER +Daron Schuppe,MANAGER +Mr. Amira Marvin,MANAGER +Boris Morar,MANAGER +Esperanza Batz,ENGINEER +Dortha Macejkovic I,MANAGER +Porter Dach V,ENGINEER +Kailyn Flatley I,MANAGER +Celine O'Keefe,MANAGER +Obie Rodriguez,MANAGER +Cade Gorczany,ENGINEER +Myles Shanahan,ENGINEER +Jayne Wiza,ENGINEER +Julius Huel,MANAGER +Ms. Rowena Kihn,ENGINEER +Ena Wehner,MANAGER +Clovis Cartwright,MANAGER +Mr. Marcelo D'Amore,MANAGER +Meggie Prosacco,ENGINEER +Lisa Schamberger PhD,ENGINEER +Mrs. Lyda Bayer,MANAGER +Newell Hettinger,MANAGER +Melany Wolf,MANAGER +Emil Schaefer,MANAGER +Samson Trantow,MANAGER +Maida Marquardt,ENGINEER +Johnpaul Howe MD,MANAGER +Mrs. Adah Lubowitz,ENGINEER +Ms. Imelda Kohler,MANAGER +Manuela Frami I,ENGINEER +Noelia Padberg,MANAGER +Una Eichmann DDS,ENGINEER +Elta Nolan,ENGINEER +Jaron Wyman,ENGINEER +Kayla Windler Sr.,MANAGER +Mrs. Zetta Stiedemann,MANAGER +Dr. Abner Adams,MANAGER +Brenden Ortiz,MANAGER +German Funk,ENGINEER +Mr. Liliane Konopelski,MANAGER +Jarrett Morar,MANAGER +Parker Huels,MANAGER +Mrs. Alvah Bayer,ENGINEER +Wilhelm Parker Jr.,ENGINEER +Leo Mertz,ENGINEER +Corine Hills,ENGINEER +Coy Raynor Sr.,ENGINEER +Mallie Streich DVM,ENGINEER +Daisy Hoeger,MANAGER +Michelle Hickle,ENGINEER +Nolan Douglas,ENGINEER +Vivian Bernier,ENGINEER +Brigitte Toy,ENGINEER +Aniyah Schoen,ENGINEER +Emmie Bins,ENGINEER +Mazie Weimann,MANAGER +Carole Aufderhar,ENGINEER +Bernhard O'Kon,ENGINEER +Flavio Moore Sr.,MANAGER +Justina Wuckert,ENGINEER +Meredith Jones,ENGINEER +Gene Champlin,MANAGER +Clare Fay,MANAGER +Lesly Johnston II,MANAGER +Cristian Kling,MANAGER +Candido Littel,MANAGER +Mrs. Gregory Ritchie,ENGINEER +Lucio Sawayn,MANAGER +Derick Rath DVM,MANAGER +Gabriella Dietrich,ENGINEER +Lula Spencer DVM,ENGINEER +Horacio Kulas,ENGINEER +Davin Vandervort DDS,ENGINEER +Ms. Avery Wisoky,ENGINEER +Talon Williamson MD,MANAGER +Gerald Hahn,MANAGER +Ettie Yost,MANAGER +Abdullah Mosciski,ENGINEER +Mrs. Marielle Bosco,ENGINEER +Kory Batz,MANAGER +Noelia Kovacek,ENGINEER +Kyleigh Nienow,MANAGER +Alize Lind,ENGINEER +Ellsworth Altenwerth,ENGINEER +Domenic Mayer,ENGINEER +Ms. Geovanny Satterfield,ENGINEER +Ella Daniel,MANAGER +Kylee Bogisich PhD,ENGINEER +Ryder Wilkinson Sr.,MANAGER +Marina Schaefer,ENGINEER +Ms. Paige Bartell,MANAGER +Mitchel Murray,ENGINEER +Tyler Quigley,MANAGER +Veronica Kreiger,ENGINEER +Halie Goldner,ENGINEER +Ryder Lakin,ENGINEER +Chloe Legros,ENGINEER +Dariana O'Conner,ENGINEER +Era Bins Jr.,ENGINEER +Laila Reichert,MANAGER +Dedrick Kuhic V,ENGINEER +Haylee Price Jr.,ENGINEER +Callie Shields,MANAGER +Jarrod Fahey,MANAGER +Earlene Cremin,ENGINEER +Ellie Bergstrom,MANAGER +Armando Grady,ENGINEER +Dr. Lamar Hessel,ENGINEER +Joe Runolfsson Sr.,ENGINEER +Manley Oberbrunner,MANAGER +Meta Weissnat II,ENGINEER +Dagmar Batz IV,ENGINEER +Susie Bayer,ENGINEER +Randi Howell,ENGINEER +Joanne Rau,MANAGER +Buck Stark,ENGINEER +Shane Donnelly,ENGINEER +Quincy Casper V,ENGINEER +Lafayette Grimes,MANAGER +Mrs. Jody Beer,ENGINEER +Miss Corene Schamberger,ENGINEER +Dr. Jesse Baumbach,ENGINEER +Brenna Quigley Jr.,MANAGER +Ms. Viviane Bins,ENGINEER +Edgar Johnson,MANAGER +Mr. Tracy Beier,ENGINEER +Juvenal Ortiz,ENGINEER +Dr. Nat Pagac,MANAGER +Julio Mitchell,ENGINEER +Clarissa Mraz Jr.,ENGINEER +Dustin Grady,MANAGER +Brennon Bayer,ENGINEER +Mrs. Birdie Nienow,MANAGER +Randal Sauer Sr.,ENGINEER +Verda Kozey,ENGINEER +Chesley Hickle,ENGINEER +Miss Rico Block,ENGINEER +Gaetano Lindgren,MANAGER +Hope Hauck,ENGINEER +Percival O'Connell,ENGINEER +Melyna Leuschke,MANAGER +Adan Collins,MANAGER +Daryl Bashirian,ENGINEER +Kara Welch,MANAGER +Norberto Swift,ENGINEER +Effie Champlin Jr.,ENGINEER +Dr. Julia Metz,MANAGER +Janice Witting,MANAGER +Bruce Eichmann,MANAGER +Isobel Swift,ENGINEER +Earnestine Mayer MD,MANAGER +Michele Bashirian,ENGINEER +Janick Crona,ENGINEER +Hank Fisher III,MANAGER +Miss Aliya Skiles,ENGINEER +Herbert Orn,MANAGER +Aglae Baumbach,ENGINEER +Gayle Carter,ENGINEER +Creola Kautzer,MANAGER +Theresa Hauck,MANAGER +Ms. Patience Wintheiser,MANAGER +Sebastian Rutherford DDS,ENGINEER +Ara Pollich I,ENGINEER +Carlos Baumbach,MANAGER +Hulda Schroeder DVM,ENGINEER +Stanton Torp,MANAGER +Ceasar Franecki,MANAGER +Lelah Miller,ENGINEER +Wyman Schultz,MANAGER +Mae Harris V,MANAGER +Hortense Koelpin,ENGINEER +Wilmer Deckow,ENGINEER +Zaria Ferry,ENGINEER +Pierre Cronin,ENGINEER +Ms. Brenna Leffler,ENGINEER +Dr. Keara Price,ENGINEER +Jane Schroeder,MANAGER +Reymundo Heathcote,MANAGER +Elton Schiller II,MANAGER +Irma Blanda,MANAGER +Mireya Turner,MANAGER +Dawson Streich,MANAGER +Gianni Cassin,MANAGER +Johnathan Kuhic V,MANAGER +Lacey Sawayn,ENGINEER diff --git a/internal/service/comprehend/test-fixtures/generate/entity_recognizer/main.go b/internal/service/comprehend/test-fixtures/generate/entity_recognizer/main.go new file mode 100644 index 00000000000..3247cfdeedf --- /dev/null +++ b/internal/service/comprehend/test-fixtures/generate/entity_recognizer/main.go @@ -0,0 +1,99 @@ +//go:build generate +// +build generate + +package main + +import ( + "encoding/csv" + "fmt" + "log" + "math/rand" + "os" + "strconv" + "strings" + + "syreclabs.com/go/faker" +) + +func main() { + var entities = []string{ + "ENGINEER", + "MANAGER", + } + + var sentences = []string{ + "%[1]s is a %[2]s in the high tech industry.", + "%[1]s has been a %[2]s for 14 years.", + "Our latest new employee, %[1]s, has been a %[2]s in the industry for 4 years.", + "Announcing %[2]s %[1]s.", + "Help me welcome our newest %[2]s, %[1]s.", + "%[1]s is retiring as a %[2]s.", + "%[1]s has been an %[2]s for over a decade.", + "%[1]s is a %[2]s with Example Corp.", + "%[1]s will be the new %[2]s for the team.", + "%[1]s, an %[2]s, will be presenting the award.", + "%[1]s joins us as an %[2]s on the Example project.", + "%[1]s is a %[2]s.", + } + + log.SetFlags(0) + + seed := int64(1) // Default rand seed + rand.Seed(seed) + faker.Seed(seed) + + entitiesFile, err := os.OpenFile("./test-fixtures/entity_recognizer/entitylist.csv", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + log.Fatalf("error opening file %q: %s", "entitylist.csv", err) + } + defer closeFile(entitiesFile, "entitylist.csv") + + if _, err := fmt.Fprintln(entitiesFile, "Text,Type"); err != nil { + log.Fatalf("error writing to file %q: %s", "entitylist.csv", err) + } + + documentFile, err := os.OpenFile("./test-fixtures/entity_recognizer/documents.txt", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + log.Fatalf("error opening file %q: %s", "documents.txt", err) + } + defer closeFile(documentFile, "documents.txt") + + annotationsFile, err := os.OpenFile("./test-fixtures/entity_recognizer/annotations.csv", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + log.Fatalf("error opening file %q: %s", "annotations.csv", err) + } + defer closeFile(annotationsFile, "annotations.csv") + annotationsWriter := csv.NewWriter(annotationsFile) + if err := annotationsWriter.Write([]string{"File", "Line", "Begin Offset", "End Offset", "Type"}); err != nil { + log.Fatalf("error writing to file %q: %s", "annotations.csv", err) + } + + for i := 0; i < 1000; i++ { + name := faker.Name().Name() + entity := entities[rand.Intn(len(entities))] + + if _, err := fmt.Fprintf(entitiesFile, "%s,%s\n", name, entity); err != nil { + log.Fatalf("error writing to file %q: %s", "entitylist.csv", err) + } + + sentence := sentences[rand.Intn(len(sentences))] + line := fmt.Sprintf(sentence, name, strings.ToLower(entity)) + if _, err := fmt.Fprintln(documentFile, line); err != nil { + log.Fatalf("error writing to file %q: %s", "documents.txt", err) + } + + offset := strings.Index(line, name) + end := offset + len(name) + if err := annotationsWriter.Write([]string{"documents.txt", strconv.Itoa(i), strconv.Itoa(offset), strconv.Itoa(end), entity}); err != nil { + log.Fatalf("error writing to file %q: %s", "annotations.csv", err) + } + } + + annotationsWriter.Flush() +} + +func closeFile(f *os.File, name string) { + if err := f.Close(); err != nil { + log.Fatalf("error closing file %q: %s", name, err) + } +} From d954ecbded687c52e205596a9d45e0c8a368dfb1 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 11 Aug 2022 01:43:50 -0700 Subject: [PATCH 25/29] Adds tests for annotations --- .../service/comprehend/entity_recognizer.go | 46 ++- .../comprehend/entity_recognizer_test.go | 376 ++++++++++++++++-- ...comprehend_entity_recognizer.html.markdown | 2 +- 3 files changed, 388 insertions(+), 36 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 3af44d06f6c..8a91633e3b9 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -62,9 +62,10 @@ func ResourceEntityRecognizer() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "annotations": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"input_data_config.0.annotations", "input_data_config.0.entity_list"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "s3_uri": { @@ -147,9 +148,10 @@ func ResourceEntityRecognizer() *schema.Resource { }, }, "entity_list": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"input_data_config.0.entity_list", "input_data_config.0.annotations"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "s3_uri": { @@ -241,10 +243,6 @@ func ResourceEntityRecognizer() *schema.Resource { CustomizeDiff: customdiff.All( verify.SetTagsDiff, func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { - if diff.Id() == "" { - return nil - } - tfMap := getInputDataConfig(diff) if tfMap == nil { return nil @@ -260,6 +258,34 @@ func ResourceEntityRecognizer() *schema.Resource { } } + return nil + }, + func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + tfMap := getInputDataConfig(diff) + if tfMap == nil { + return nil + } + + documents := expandDocuments(tfMap["documents"].([]interface{})) + if documents == nil { + return nil + } + + annotations := expandAnnotations(tfMap["annotations"].([]interface{})) + if annotations == nil { + return nil + } + + if documents.TestS3Uri != nil { + if annotations.TestS3Uri == nil { + return errors.New("input_data_config.annotations.test_s3_uri must be set when input_data_config.documents.test_s3_uri is set") + } + } else { + if annotations.TestS3Uri != nil { + return errors.New("input_data_config.documents.test_s3_uri must be set when input_data_config.annotations.test_s3_uri is set") + } + } + return nil }, ), diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index dabc61da999..47c9f73d4a4 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -280,7 +280,7 @@ func TestAccComprehendEntityRecognizer_versionNamePrefix(t *testing.T) { }) } -func TestAccComprehendEntityRecognizer_testDocuments(t *testing.T) { +func TestAccComprehendEntityRecognizer_documents_testDocuments(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") } @@ -333,6 +333,166 @@ func TestAccComprehendEntityRecognizer_testDocuments(t *testing.T) { }) } +func TestAccComprehendEntityRecognizer_annotations_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_annotations_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, uniqueIDPattern()))), + resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.annotations.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.augmented_manifests.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.data_format", string(types.EntityRecognizerDataFormatComprehendCsv)), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.0.input_format", string(types.InputFormatOneDocPerLine)), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.0.test_s3_uri", ""), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "0"), + resource.TestCheckResourceAttr(resourceName, "language_code", "en"), + resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), + acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", resource.UniqueIdPrefix), + resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_annotations_testDocuments(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var entityrecognizer types.EntityRecognizerProperties + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_comprehend_entity_recognizer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_annotations_testDocuments(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEntityRecognizerExists(resourceName, &entityrecognizer), + testAccCheckEntityRecognizerPublishedVersions(resourceName, 1), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "data_access_role_arn", "aws_iam_role.test", "arn"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "comprehend", regexp.MustCompile(fmt.Sprintf(`entity-recognizer/%s/version/%s$`, rName, uniqueIDPattern()))), + resource.TestCheckResourceAttr(resourceName, "input_data_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.annotations.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.augmented_manifests.#", "0"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.data_format", string(types.EntityRecognizerDataFormatComprehendCsv)), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.documents.0.input_format", string(types.InputFormatOneDocPerLine)), + resource.TestCheckResourceAttrSet(resourceName, "input_data_config.0.documents.0.test_s3_uri"), + resource.TestCheckResourceAttr(resourceName, "input_data_config.0.entity_list.#", "0"), + resource.TestCheckResourceAttr(resourceName, "language_code", "en"), + resource.TestCheckResourceAttr(resourceName, "model_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"), + acctest.CheckResourceAttrNameGenerated(resourceName, "version_name"), + resource.TestCheckResourceAttr(resourceName, "version_name_prefix", resource.UniqueIdPrefix), + resource.TestCheckResourceAttr(resourceName, "volume_kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_annotations_validateNoTestDocuments(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_annotations_noTestDocuments(rName), + ExpectError: regexp.MustCompile("input_data_config.documents.test_s3_uri must be set when input_data_config.annotations.test_s3_uri is set"), + }, + }, + }) +} + +func TestAccComprehendEntityRecognizer_annotations_validateNoTestAnnotations(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.ComprehendEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ComprehendEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEntityRecognizerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEntityRecognizerConfig_annotations_noTestAnnotations(rName), + ExpectError: regexp.MustCompile("input_data_config.annotations.test_s3_uri must be set when input_data_config.documents.test_s3_uri is set"), + }, + }, + }) +} + func TestAccComprehendEntityRecognizer_KMSKeys_CreateIDs(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -733,10 +893,6 @@ func TestAccComprehendEntityRecognizer_DefaultTags_providerOnly(t *testing.T) { }) } -// TODO: test deletion from in-error state. Try insufficient permissions to force error - -// TODO: add test for catching, e.g. permission errors in training - func testAccCheckEntityRecognizerDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ComprehendConn ctx := context.Background() @@ -873,7 +1029,7 @@ func testAccEntityRecognizerConfig_basic(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -911,7 +1067,7 @@ func testAccEntityRecognizerConfig_versionName(rName, vName, key, value string) return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -954,7 +1110,7 @@ func testAccEntityRecognizerConfig_versionNameEmpty(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -993,7 +1149,7 @@ func testAccEntityRecognizerConfig_versionNameNotSet(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1031,7 +1187,7 @@ func testAccEntityRecognizerConfig_versioNamePrefix(rName, versionNamePrefix str return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1070,7 +1226,7 @@ func testAccEntityRecognizerConfig_testDocuments(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1090,7 +1246,7 @@ resource "aws_comprehend_entity_recognizer" "test" { documents { s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" - test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" } entity_list { @@ -1105,11 +1261,167 @@ resource "aws_comprehend_entity_recognizer" "test" { `, rName)) } +func testAccEntityRecognizerConfig_annotations_basic(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_annotations, + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + annotations { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.annotations.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + ] +} +`, rName)) +} + +func testAccEntityRecognizerConfig_annotations_testDocuments(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_annotations, + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + annotations { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.annotations.id}" + test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.annotations.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + ] +} +`, rName)) +} + +func testAccEntityRecognizerConfig_annotations_noTestDocuments(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_annotations, + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + annotations { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.annotations.id}" + test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.annotations.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + ] +} +`, rName)) +} + +func testAccEntityRecognizerConfig_annotations_noTestAnnotations(rName string) string { + return acctest.ConfigCompose( + testAccEntityRecognizerBasicRoleConfig(rName), + testAccEntityRecognizerS3BucketConfig(rName), + testAccEntityRecognizerConfig_S3_annotations, + fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_comprehend_entity_recognizer" "test" { + name = %[1]q + + data_access_role_arn = aws_iam_role.test.arn + + language_code = "en" + input_data_config { + entity_types { + type = "ENGINEER" + } + entity_types { + type = "MANAGER" + } + + documents { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + test_s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.documents.id}" + } + + annotations { + s3_uri = "s3://${aws_s3_bucket.test.bucket}/${aws_s3_object.annotations.id}" + } + } + + depends_on = [ + aws_iam_role_policy.test, + ] +} +`, rName)) +} + func testAccEntityRecognizerConfig_kmsKeyIds(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1185,7 +1497,7 @@ func testAccEntityRecognizerConfig_kmsKeyARNs(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1261,7 +1573,7 @@ func testAccEntityRecognizerConfig_kmsKeys_None(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1299,7 +1611,7 @@ func testAccEntityRecognizerConfig_kmsKeys_Set(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1375,7 +1687,7 @@ func testAccEntityRecognizerConfig_kmsKeys_Update(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1451,7 +1763,7 @@ func testAccEntityRecognizerConfig_tags0(rName string) string { return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1491,7 +1803,7 @@ func testAccEntityRecognizerConfig_tags1(rName, tagKey1, tagValue1 string) strin return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1533,7 +1845,7 @@ func testAccEntityRecognizerConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tag return acctest.ConfigCompose( testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1684,7 +1996,7 @@ func testAccEntityRecognizerConfig_vpcConfig(rName string) string { testAccEntityRecognizerConfig_vpcRole(), testAccEntityRecognizerS3BucketConfig(rName), configVPCWithSubnetsAndDNS(rName, subnetCount), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1805,7 +2117,7 @@ func testAccEntityRecognizerConfig_vpcConfig_Update(rName string) string { testAccEntityRecognizerConfig_vpcRole(), testAccEntityRecognizerS3BucketConfig(rName), configVPCWithSubnetsAndDNS(rName, subnetCount), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1924,7 +2236,7 @@ func testAccEntityRecognizerConfig_vpcConfig_None(rName string) string { testAccEntityRecognizerBasicRoleConfig(rName), testAccEntityRecognizerConfig_vpcRole(), testAccEntityRecognizerS3BucketConfig(rName), - testAccEntityRecognizerConfig_S3_basic, + testAccEntityRecognizerConfig_S3_entityList, fmt.Sprintf(` data "aws_partition" "current" {} @@ -1960,7 +2272,7 @@ resource "aws_comprehend_entity_recognizer" "test" { `, rName)) } -const testAccEntityRecognizerConfig_S3_basic = ` +const testAccEntityRecognizerConfig_S3_entityList = ` resource "aws_s3_object" "documents" { bucket = aws_s3_bucket.test.bucket key = "documents.txt" @@ -1972,4 +2284,18 @@ resource "aws_s3_object" "entities" { key = "entitylist.csv" source = "test-fixtures/entity_recognizer/entitylist.csv" } - ` +` + +const testAccEntityRecognizerConfig_S3_annotations = ` +resource "aws_s3_object" "documents" { + bucket = aws_s3_bucket.test.bucket + key = "documents.txt" + source = "test-fixtures/entity_recognizer/documents.txt" +} + +resource "aws_s3_object" "annotations" { + bucket = aws_s3_bucket.test.bucket + key = "entitylist.csv" + source = "test-fixtures/entity_recognizer/annotations.csv" +} +` diff --git a/website/docs/r/comprehend_entity_recognizer.html.markdown b/website/docs/r/comprehend_entity_recognizer.html.markdown index 7f183859db5..a2c9b8ba1cd 100644 --- a/website/docs/r/comprehend_entity_recognizer.html.markdown +++ b/website/docs/r/comprehend_entity_recognizer.html.markdown @@ -99,7 +99,7 @@ The following arguments are optional: See the [`documents` Configuration Block](#documents-configuration-block) section below. * `entity_list` - (Optional) Specifies location of the entity list data. See the [`entity_list` Configuration Block](#entity_list-configuration-block) section below. - One of `entity_list` or`annotations` is required. + One of `entity_list` or `annotations` is required. * `entity_types` - (Required) Set of entity types to be recognized. Has a maximum of 25 items. See the [`entity_types` Configuration Block](#entity_types-configuration-block) section below. From c5f85655d48188983d66a409fdabdc57b9611eaf Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 11 Aug 2022 01:47:27 -0700 Subject: [PATCH 26/29] Adds CHANGELOG entry --- .changelog/26244.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/26244.txt diff --git a/.changelog/26244.txt b/.changelog/26244.txt new file mode 100644 index 00000000000..c5c8751d0e1 --- /dev/null +++ b/.changelog/26244.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_comprehend_entity_recognizer +``` From 0590152a21b5dee9dd7eff54d8b598b8ed069142 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 11 Aug 2022 09:22:09 -0700 Subject: [PATCH 27/29] Uses `flex` `StringValue` functions --- .../service/comprehend/entity_recognizer.go | 13 +++---- internal/service/comprehend/flex.go | 35 ------------------- 2 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 internal/service/comprehend/flex.go diff --git a/internal/service/comprehend/entity_recognizer.go b/internal/service/comprehend/entity_recognizer.go index 8a91633e3b9..119bf790db8 100644 --- a/internal/service/comprehend/entity_recognizer.go +++ b/internal/service/comprehend/entity_recognizer.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/create" awsdiag "github.com/hashicorp/terraform-provider-aws/internal/diag" "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/flex" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -832,7 +833,7 @@ func flattenAugmentedManifestsListItem(apiObject *types.AugmentedManifestsListIt } m := map[string]interface{}{ - "attribute_names": FlattenStringList(apiObject.AttributeNames), + "attribute_names": flex.FlattenStringValueList(apiObject.AttributeNames), "s3_uri": aws.ToString(apiObject.S3Uri), "document_type": apiObject.DocumentType, "split": apiObject.Split, @@ -884,8 +885,8 @@ func flattenVPCConfig(apiObject *types.VpcConfig) []interface{} { } m := map[string]interface{}{ - "security_group_ids": FlattenStringSet(apiObject.SecurityGroupIds), - "subnets": FlattenStringSet(apiObject.Subnets), + "security_group_ids": flex.FlattenStringValueSet(apiObject.SecurityGroupIds), + "subnets": flex.FlattenStringValueSet(apiObject.Subnets), } return []interface{}{m} @@ -1009,7 +1010,7 @@ func expandAugmentedManifestsListItem(tfMap map[string]interface{}) *types.Augme } a := &types.AugmentedManifestsListItem{ - AttributeNames: ExpandStringList(tfMap["attribute_names"].([]interface{})), + AttributeNames: flex.ExpandStringValueList(tfMap["attribute_names"].([]interface{})), S3Uri: aws.String(tfMap["s3_uri"].(string)), DocumentType: types.AugmentedManifestsDocumentTypeFormat(tfMap["document_type"].(string)), Split: types.Split(tfMap["split"].(string)), @@ -1067,8 +1068,8 @@ func expandVPCConfig(tfList []interface{}) *types.VpcConfig { tfMap := tfList[0].(map[string]interface{}) a := &types.VpcConfig{ - SecurityGroupIds: ExpandStringSet(tfMap["security_group_ids"].(*schema.Set)), - Subnets: ExpandStringSet(tfMap["subnets"].(*schema.Set)), + SecurityGroupIds: flex.ExpandStringValueSet(tfMap["security_group_ids"].(*schema.Set)), + Subnets: flex.ExpandStringValueSet(tfMap["subnets"].(*schema.Set)), } return a diff --git a/internal/service/comprehend/flex.go b/internal/service/comprehend/flex.go deleted file mode 100644 index 89c79e39026..00000000000 --- a/internal/service/comprehend/flex.go +++ /dev/null @@ -1,35 +0,0 @@ -package comprehend - -import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - -// Takes the result of flatmap.Expand for an array of strings -// and returns a []string -func ExpandStringList(configured []interface{}) []string { - vs := make([]string, 0, len(configured)) - for _, v := range configured { - val, ok := v.(string) - if ok && val != "" { - vs = append(vs, val) - } - } - return vs -} - -func ExpandStringSet(configured *schema.Set) []string { - return ExpandStringList(configured.List()) -} - -// Takes list of strings. Expand to an array -// of raw strings and returns a []interface{} -// to keep compatibility w/ schema.NewSet -func FlattenStringList(list []string) []interface{} { - vs := make([]interface{}, 0, len(list)) - for _, v := range list { - vs = append(vs, v) - } - return vs -} - -func FlattenStringSet(list []string) *schema.Set { - return schema.NewSet(schema.HashString, FlattenStringList(list)) -} From ca26e965ee07ba6295ef30425b5f50b5076efe00 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 11 Aug 2022 09:22:44 -0700 Subject: [PATCH 28/29] Updates semgrep rules for `flex` `StringValueSet` functions --- .ci/.semgrep.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.ci/.semgrep.yml b/.ci/.semgrep.yml index 0f042e76454..31bbb7f1bde 100644 --- a/.ci/.semgrep.yml +++ b/.ci/.semgrep.yml @@ -158,16 +158,18 @@ rules: - id: helper-schema-Set-extraneous-NewSet-with-flattenStringList languages: [go] - message: Prefer `flex.FlattenStringSet()` function for casting a list of string pointers to a set + message: Prefer `flex.FlattenStringSet()` or `flex.FlattenStringValueSet()` paths: include: - internal/ - pattern: schema.NewSet(schema.HashString, flex.FlattenStringList($APIOBJECT)) + patterns: + - pattern: schema.NewSet(schema.HashString, flex.FlattenStringList($APIOBJECT)) + - pattern: schema.NewSet(schema.HashString, flex.FlattenStringValueList($APIOBJECT)) severity: WARNING - id: helper-schema-Set-extraneous-expandStringList-with-List languages: [go] - message: Prefer `flex.ExpandStringSet()` function for casting a set to a list of string pointers + message: Prefer `flex.ExpandStringSet()` or `flex.ExpandStringValueSet()` paths: include: - internal/ @@ -178,6 +180,11 @@ rules: $LIST := $SET.List() ... flex.ExpandStringList($LIST) + - pattern: flex.ExpandStringValueList($SET.List()) + - pattern: | + $LIST := $SET.List() + ... + flex.ExpandStringValueList($LIST) severity: WARNING - id: helper-schema-ResourceData-GetOk-with-extraneous-conditional From 6e89d7a660644b592b44698639d035fd6ddd7b14 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 11 Aug 2022 10:00:41 -0700 Subject: [PATCH 29/29] Fixes linting errors --- .../comprehend/entity_recognizer_test.go | 11 +++-- internal/service/ec2/vpc_network_interface.go | 43 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/internal/service/comprehend/entity_recognizer_test.go b/internal/service/comprehend/entity_recognizer_test.go index 47c9f73d4a4..5632b68fc21 100644 --- a/internal/service/comprehend/entity_recognizer_test.go +++ b/internal/service/comprehend/entity_recognizer_test.go @@ -988,7 +988,7 @@ func prefixedUniqueIDPattern(prefix string) string { return fmt.Sprintf("%s[[:xdigit:]]{%d}", prefix, resource.UniqueIDSuffixLength) } -func testAccCheckEntityRecognizerPublishedVersions(name string, count int) resource.TestCheckFunc { +func testAccCheckEntityRecognizerPublishedVersions(name string, expected int) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -1012,15 +1012,20 @@ func testAccCheckEntityRecognizerPublishedVersions(name string, count int) resou RecognizerName: aws.String(name), }, } - total := 0 + count := 0 paginator := comprehend.NewListEntityRecognizersPaginator(conn, input) for paginator.HasMorePages() { output, err := paginator.NextPage(ctx) if err != nil { return err } - total += len(output.EntityRecognizerPropertiesList) + count += len(output.EntityRecognizerPropertiesList) } + + if count != expected { + return fmt.Errorf("expected %d published versions, found %d", expected, count) + } + return nil } } diff --git a/internal/service/ec2/vpc_network_interface.go b/internal/service/ec2/vpc_network_interface.go index 4c648a643a9..f4fe9e06748 100644 --- a/internal/service/ec2/vpc_network_interface.go +++ b/internal/service/ec2/vpc_network_interface.go @@ -1546,35 +1546,40 @@ func deleteLingeringLambdaENIs(ctx context.Context, g *multierror.Group, conn *e } for _, v := range networkInterfaces { - networkInterfaceID := aws.StringValue(v.NetworkInterfaceId) + v := v + g.Go(func() error { + networkInterfaceID := aws.StringValue(v.NetworkInterfaceId) - if v.Attachment != nil && aws.StringValue(v.Attachment.InstanceOwnerId) == "amazon-aws" { - networkInterface, err := WaitNetworkInterfaceAvailableAfterUse(conn, networkInterfaceID, timeout) + if v.Attachment != nil && aws.StringValue(v.Attachment.InstanceOwnerId) == "amazon-aws" { + networkInterface, err := WaitNetworkInterfaceAvailableAfterUse(conn, networkInterfaceID, timeout) - if tfresource.NotFound(err) { - continue - } + if tfresource.NotFound(err) { + return nil + } - if err != nil { - return fmt.Errorf("waiting for Lambda ENI (%s) to become available for detachment: %w", networkInterfaceID, err) + if err != nil { + return fmt.Errorf("waiting for Lambda ENI (%s) to become available for detachment: %w", networkInterfaceID, err) + } + + v = networkInterface } - v = networkInterface - } + if v.Attachment != nil { + err = DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout) + + if err != nil { + return fmt.Errorf("detaching Lambda ENI (%s): %w", networkInterfaceID, err) + } + } - if v.Attachment != nil { - err = DetachNetworkInterface(conn, networkInterfaceID, aws.StringValue(v.Attachment.AttachmentId), timeout) + err = DeleteNetworkInterface(conn, networkInterfaceID) if err != nil { - return fmt.Errorf("detaching Lambda ENI (%s): %w", networkInterfaceID, err) + return fmt.Errorf("deleting Lambda ENI (%s): %w", networkInterfaceID, err) } - } - err = DeleteNetworkInterface(conn, networkInterfaceID) - - if err != nil { - return fmt.Errorf("deleting Lambda ENI (%s): %w", networkInterfaceID, err) - } + return nil + }) } return nil