diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 7538a57a..35b96152 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,6 +12,7 @@ on: - v5 - v6 - v7 + - v8 - master jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index 09dc3e0f..eea76a86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,296 @@ -# EDGEGRID GOLANG RELEASE NOTES +# RELEASE NOTES + +## 9.0.0 (Oct 3, 2024) + +#### BREAKING CHANGES: + +* General + * Consolidated multiple sub-interfaces into a single interface for each sub-provider. + * Renamed `NTWRKLISTS` interface to `NetworkList` for `networklists` provider + * Removed `tools` package in favour of `ptr` package + +* Cloudaccess + * Changed naming of request body fields for following structures: + * `BodyParams` to `Body` in `CreateAccessKeyVersionRequest` + * `CreateAccessKeyVersionBodyParams` to `CreateAccessKeyVersionRequestBody` + +* Cloudlets + * Changed naming of request body fields for following structures: + * `BodyParams` to `Body` in `UpdatePolicyRequest` and `ClonePolicyRequest` + * `UpdatePolicyBodyParams` to `UpdatePolicyRequestBody` + * `ClonePolicyBodyParams` to `ClonePolicyRequestBody` + +* Cloudwrapper + * Changed naming of request body fields for following structures: + * `CreateConfigurationBody` to `CreateConfigurationRequestBody` + * `UpdateConfigurationBody` to `UpdateConfigurationRequestBody` + +* DNS + * Refactored parameters in following methods: + * `GetAuthorities` - from (context.Context, string) into (context.Context, `GetAuthoritiesRequest`) + * `GetNameServerRecordList` - from (context.Context, string) into (context.Context, `GetNameServerRecordListRequest`) + * `GetRecord` - from (context.Context, string, string, string) into (context.Context, `GetRecordRequest`) + * `GetRecordList` - from (context.Context, string, string, string) into (context.Context, `GetRecordListRequest`) + * `CreateRecord` - from (context.Context, *RecordBody, string, ...bool) into (context.Context, `CreateRecordRequest`) + * `UpdateRecord` - from (context.Context, *RecordBody, string, ...bool) into (context.Context, `UpdateRecordRequest`) + * `DeleteRecord` - from (context.Context, *RecordBody, string, ...bool) into (context.Context, `DeleteRecordRequest`) + * `GetRecordSets` - from (context.Context, string, ...RecordSetQueryArgs) into (context.Context, `GetRecordSetsRequest`) + * `CreateRecordSets` - from (context.Context, *RecordSets, string, ...bool) into (context.Context, `CreateRecordSetsRequest`) + * `UpdateRecordSets` - from (context.Context, *RecordSets, string, ...bool) into (context.Context, `UpdateRecordSetsRequest`) + * `ListTSIGKeys` - from (context.Context, *TSIGQueryString) into (context.Context, `ListTSIGKeysRequest`) + * `GetTSIGKeyZones` - from (context.Context, *TSIGKey) into (context.Context, `GetTSIGKeyZonesRequest`) + * `GetTSIGKeyAliases` - from (context.Context, string) into (context.Context, `GetTSIGKeyAliasesRequest`) + * `UpdateTSIGKeyBulk` - from (context.Context, *TSIGKeyBulkPost) into (context.Context, `UpdateTSIGKeyBulkRequest`) + * `GetTSIGKey` - from (context.Context, string) into (context.Context, `GetTSIGKeyRequest`) + * `DeleteTSIGKey` - from (context.Context, string) into (context.Context, `DeleteTSIGKeyRequest`) + * `UpdateTSIGKey` - from (context.Context, *TSIGKey, string) into (context.Context, `UpdateTSIGKeyRequest`) + * `ListZones` - from (context.Context, ...ZoneListQueryArgs) into (context.Context, `ListZonesRequest`) + * `GetZone` - from (context.Context, string) into (context.Context, `GetZoneRequest`) + * `GetChangeList` - from (context.Context, string) into (context.Context, `GetChangeListRequest`) + * `GetMasterZoneFile` - from (context.Context, string) into (context.Context, `GetMasterZoneFileRequest`) + * `PostMasterZoneFile` - from (context.Context, string, string) into (context.Context, `PostMasterZoneFileRequest`) + * `CreateZone` - from (context.Context, *ZoneCreate, ZoneQueryString, ...bool) into (context.Context, `CreateZoneRequest`) + * `SaveChangeList` - from (context.Context, *ZoneCreate) into (context.Context, `SaveChangeListRequest`) + * `SubmitChangeList` - from (context.Context, *ZoneCreate) into (context.Context, `SubmitChangeListRequest`) + * `UpdateZone` - from (context.Context, *ZoneCreate) into (context.Context, `UpdateZoneRequest`) + * `GetZoneNames` - from (context.Context, string) into (context.Context, `GetZoneNamesRequest`) + * `GetZoneNameTypes` - from (context.Context, string, string) into (context.Context, `GetZoneNameTypesRequest`) + * `GetBulkZoneCreateStatus` - from (context.Context, string) into (context.Context, `GetBulkZoneCreateStatusRequest`) + * `GetBulkZoneDeleteStatus` - from (context.Context, string) into (context.Context, `GetBulkZoneDeleteStatusRequest`) + * `GetBulkZoneCreateResult` - from (context.Context, string) into (context.Context, `GetBulkZoneCreateResultRequest`) + * `GetBulkZoneDeleteResult` - from (context.Context, string) into (context.Context, `GetBulkZoneDeleteResultRequest`) + * `CreateBulkZones` - from (context.Context, *BulkZonesCreate, ZoneQueryString) into (context.Context, `CreateBulkZonesRequest`) + * `DeleteBulkZones` - from (context.Context, *ZoneNameListResponse, ...bool) into (context.Context, `DeleteBulkZonesRequest`) + * `GetRdata` - from (context.Context, string, string, string) into (context.Context, `GetRdataRequest`) + * Refactored response in following methods: + * `GetAuthorities` - `*AuthorityResponse` into `*GetAuthoritiesResponse` + * `GetRecord` - `*RecordBody` into `*GetRecordResponse` + * `GetRecordList` - `*RecordSetResponse` into `*GetRecordListResponse` + * `GetRecordSets` - `*RecordSetResponse` into `*GetRecordSetsResponse` + * `GetTSIGKey` - `*TSIGKeyResponse` into `*GetTSIGKeyResponse` + * `ListTSIGKeys` - `*TSIGReportResponse` into `*ListTSIGKeysResponse` + * `GetTSIGKeyZones` - `*ZoneNameListResponse` into `*GetTSIGKeyZonesResponse` + * `GetTSIGKeyAliases` - `*ZoneNameListResponse` into `*GetTSIGKeyAliasesResponse` + * `GetZone` - `*ZoneResponse` into `*GetZoneResponse` + * `GetChangeList` - `*ChangeListResponse` into `*GetChangeListResponse` + * `GetZoneNames` - `*ZoneNamesResponse` into `*GetZoneNamesResponse` + * `GetZoneNameTypes` - `*ZoneNameTypesResponse` into `*GetZoneNameTypesResponse` + * `GetBulkZoneCreateStatus` - `*BulkStatusResponse` into `*GetBulkZoneCreateStatusResponse` + * `GetBulkZoneDeleteStatus` - `*BulkStatusResponse` into `*GetBulkZoneDeleteStatusResponse` + * `GetBulkZoneCreateResult` - `*BulkCreateResultResponse` into `*GetBulkZoneCreateResultResponse` + * `GetBulkZoneDeleteResult` - `*BulkDeleteResultResponse` into `*GetBulkZoneDeleteResultResponse` + * `CreateBulkZones` - `*BulkZonesResponse` into `*CreateBulkZonesResponse` + * `DeleteBulkZones` - `*BulkZonesResponse` into `*DeleteBulkZonesResponse` + * Removed following interfaces: + * `Authorities` + * `Data` + * `Records` + * `Recordsets` + * `TSIGKeys` + * `Zones` + * Renamed following methods: + * `SaveChangelist` into `SaveChangeList` + * `SubmitChangelist` into `SubmitChangeList` + * `TSIGKeyBulkUpdate` into `UpdateTSIGKeyBulk` + +* EdgeKV + * For the `CreateEdgeKVAccessTokenRequest`, removed the `Expiry` field and added the `RestrictToEdgeWorkerIDs` field. + * For the `CreateEdgeKVAccessTokenResponse`, removed the `Expiry` and `Value` fields, and added these fields: + * `AllowOnProduction` + * `AllowOnStaging` + * `CPCode` + * `IssueDate` + * `LatestRefreshDate` + * `NamespacePermissions` + * `NextScheduledRefreshDate` + * `RestrictToEdgeWorkerIDs` + * `TokenActivationStatus` + * Added these fields to the `EdgeKVAccessToken` method: + * `TokenActivationStatus` + * `IssueDate` + * `LatestRefreshDate` + * `NextScheduledRefreshDate` + +* Edgeworkers + * Changed naming of request body fields for these structures: + * `EdgeWorkerIDBodyRequest` to `EdgeWorkerIDRequestBody` + +* GTM + * Refactored parameters in these methods: + * `ListASMaps` - from (context.Context, string) into (context.Context, `ListASMapsRequest`) + * `GetASMap` - from (context.Context, string, string) into (context.Context, `GetASMapRequests`) + * `CreateASMap` - from (context.Context, *ASMap, string) into (context.Context, `CreateASMapRequest`) + * `UpdateASMap` - from (context.Context, *ASMap, string) into (context.Context, `UpdateASMapRequest`) + * `DeleteASMap` - from (context.Context, *ASMap, string) into (context.Context, `DeleteASMapRequest`) + * `ListCIDRMaps` - from (context.Context, string) into (context.Context, `ListCIDRMapsRequest`) + * `GetCIDRMap` - from (context.Context, string, string) into (context.Context, `GetCIDRMapRequest`) + * `CreateCIDRMap` - from (context.Context, *CIDRMap, string) into (context.Context, `CreateCIDRMapRequest`) + * `UpdateCIDRMap` - from (context.Context, *CIDRMap, string) into (context.Context, `UpdateCIDRMapRequest`) + * `DeleteCIDRMap` - from (context.Context, *CIDRMap, string) into (context.Context, `DeleteCIDRMapRequest`) + * `ListDatacenters` - from (context.Context, string) into (context.Context, `ListDatacentersRequest`) + * `GetDatacenter` - from (context.Context, int, string) into (context.Context, `GetDatacenterRequest`) + * `CreateDatacenter` - from (context.Context, *Datacenter, string) into (context.Context, `CreateDatacenterRequest`) + * `UpdateDatacenter` - from (context.Context, *Datacenter, string) into (context.Context, `UpdateDatacenterRequest`) + * `DeleteDatacenter` - from (context.Context, *Datacenter, string) into (context.Context, `DeleteDatacenterRequest`) + * `GetDomainStatus` - from (context.Context, string) into (context.Context, `GetDomainStatusRequest`) + * `GetDomain` - from (context.Context, string) into (context.Context, `GetDomainRequest`) + * `CreateDomain` - from (context.Context, *Domain, map[string]string) into (context.Context, `CreateDomainRequest`) + * `UpdateDomain` - from (context.Context, *Domain, map[string]string) into (context.Context, `UpdateDomainRequest`) + * `DeleteDomain` - from (context.Context, *Domain) into (context.Context, `DeleteDomainRequest`) + * `ListGeoMaps` - from (context.Context, string) into (context.Context, `ListGeoMapsRequest`) + * `GetGeoMap` - from (context.Context, string, string) into (context.Context, `GetGeoMapRequest`) + * `CreateGeoMap` - from (context.Context, *GeoMap, string) into (context.Context, `CreateGeoMapRequest`) + * `UpdateGeoMap` - from (context.Context, *GeoMap, string) into (context.Context, `UpdateGeoMapRequest`) + * `DeleteGeoMap` - from (context.Context, *GeoMap, string) into (context.Context, `DeleteGeoMapRequest`) + * `ListProperties` - from (context.Context, string) into (context.Context, `ListPropertiesRequest`) + * `GetProperty` - from (context.Context, string, string) into (context.Context, `GetPropertyRequest`) + * `CreateProperty` - from (context.Context, *Property, string) into (context.Context, `CreatePropertyRequest`) + * `UpdateProperty` - from (context.Context, *Property, string) into (context.Context, `UpdatePropertyRequest`) + * `DeleteProperty` - from (context.Context, *Property, string) into (context.Context, `DeletePropertyRequest`) + * `ListResources` - from (context.Context, string) into (context.Context, `ListResourcesRequest`) + * `GetResource` - from (context.Context, string, string) into (context.Context, `GetResourceRequest`) + * `CreateResource` - from (context.Context, *Resource, string) into (context.Context, `CreateResourceRequest`) + * `UpdateResource` - from (context.Context, *Resource, string) into (context.Context, `UpdateResourceRequest`) + * `DeleteResource` - from (context.Context, *Resource, string) into (context.Context, `DeleteResourceRequest`) + * Refactored response in these methods: + * `ListASMaps` - `[]*ASMap` into `[]ASMap` + * `GetASMap` - `*ASMap` into `*GetASMapResponse` + * `CreateASMap` - `*ASMapResponse` into `*CreateASMapResponse` + * `UpdateASMap` - `*ResponseStatus` into `*UpdateASMapResponse` + * `DeleteASMap` -`*ResponseStatus` into `*DeleteASMapResponse` + * `ListCIDRMaps` - `[]*CIDRMap` into `[]CIDRMap` + * `GetCIDRMap` - `*CIDRMap` into `*GetCIDRMapResponse` + * `CreateCIDRMap` - `*CIDRMapResponse` into `*CreateCIDRMapResponse` + * `UpdateCIDRMap` - `*ResponseStatus` into `*UpdateCIDRMapResponse` + * `DeleteCIDRMap` - `*ResponseStatus` into `*DeleteCIDRMapResponse` + * `ListDatacenters` - `[]*Datacenter` into `[]Datacenter` + * `CreateDatacenter` - `*DatacenterResponse` into `*CreateDatacenterResponse` + * `UpdateDatacenter` - `*ResponseStatus` into `*UpdateDatacenterResponse` + * `DeleteDatacenter` - `*ResponseStatus` into `*DeleteDatacenterResponse` + * `ListDomains` - `[]*DomainItem` into `[]DomainItem` + * `GetDomain` - `*Domain` into `*GetDomainResponse` + * `CreateDomain` - `*DomainResponse` into `*CreateDomainResponse` + * `UpdateDomain` - `*ResponseStatus` into `*UpdateDomainResponse` + * `DeleteDomain` - `*ResponseStatus` into `*DeleteDomainResponse` + * `GetDomainStatus` - `*ResponseStatus` into `*GetDomainStatusResponse` + * `ListGeoMaps` - `[]*GeoMap` into `[]GeoMap` + * `GetGeoMap` - `*GeoMap` into `*GetGeoMapResponse` + * `CreateGeoMap` - `*GeoMapResponse` into `*CreateGeoMapResponse` + * `UpdateGeoMap` - `*ResponseStatus` into `*UpdateGeoMapResponse` + * `DeleteGeoMap` - `*ResponseStatus` into `*DeleteGeoMapResponse` + * `ListProperties` - `[]*Property` into `[]Property` + * `GetProperty` - `*Property` into `*GetPropertyResponse` + * `CreateProperty` - `*PropertyResponse` into `*CreatePropertyResponse` + * `UpdateProperty` - `*ResponseStatus` into `*UpdatePropertyResponse` + * `DeleteProperty` - `*ResponseStatus` into `*DeletePropertyResponse` + * `ListResources` - `[]*Resource` into `[]Resource` + * `GetResource` - `*Resource` into `*GetResourceResponse` + * `CreateResource` - `*ResourceResponse` into `*CreateResourceResponse` + * `UpdateResource` - `*ResponseStatus` into `*UpdateResourceResponse` + * `DeleteResource` - `*ResponseStatus` into `*DeleteResourceResponse` + * Extended response for these methods - previously only status was returned, now status and resource are returned: + * `UpdateASMap` + * `DeleteASMap` + * `UpdateCIDRMap` + * `DeleteCIDRMap` + * `UpdateDatacenter` + * `DeleteDatacenter` + * `UpdateDomain` + * `UpdateGeoMap` + * `DeleteGeoMap` + * `UpdateProperty` + * `DeleteProperty` + * `UpdateResource` + * `DeleteResource` + * Removed these interfaces: + * `ASMaps` + * `CIDRMaps` + * `Datacenters` + * `Domains` + * `GeoMaps` + * `Properties` + * `Resources` + +* IAM + * Migrated V2 endpoints to V3. + * Improved date handling to use `time.Time` instead of `string`. + * Changed fields in these structures: + * `Users` + * `LastLoginDate`. Changed the field data type from `string` to `time.Time` + * `PasswordExpiryDate`. Changed the field data type from `string` to `time.Time` + * `UserListItem` + * `LastLoginDate`. Changed the field data type from `string` to `time.Time` + * `Role` + * `CreatedDate`. Changed the field data type from `string` to `time.Time` + * `ModifiedDate`. Changed the field data type from `string` to `time.Time` + * `RoleUser` + * `LastLoginDate`. Changed the field data type from `string` to `time.Time` + * `GroupUser` + * `LastLoginDate`. Changed the field data type from `string` to `time.Time` + * Changed the `Notifications` field to a pointer type in these structures: + * `CreateUserRequest` + * `UpdateUserNotificationsRequest` + * Added the required `AdditionalAuthentication` field to the `CreateUserRequest` method. + * Made the `Notifications` field required in the `UpdateUserNotifications` method. + +* PAPI + * Removed the `rule_format` and `product_id` fields from the `Property` structure, as this information is populated in the `GetPropertyVersion` method. + +#### FEATURES/ENHANCEMENTS: + +* APPSEC + * Added the `Exceptions` field to these structures: + * `GetSiemSettingsResponse` + * `GetSiemSettingResponse` + * `UpdateSiemSettingsRequest` + * `UpdateSiemSettingsResponse` + * Added the `Source` field to the `GetExportConfigurationRequest` method and the `TargetProduct` field to the `GetExportConfigurationResponse` method. + +* IAM + * Updated structures: + * `User` with `AdditionalAuthenticationConfigured` and `Actions` + * `UserListItem` with `AdditionalAuthenticationConfigured` and `AdditionalAuthentication` + * `UserBasicInfo` with `AdditionalAuthentication` + * `UserActions` with `CanGenerateBypassCode` + * `UserNotificationOptions` with `APIClientCredentialExpiry` + * Added new methods: + * [UpdateMFA](https://techdocs.akamai.com/iam-api/reference/put-user-profile-additional-authentication) + * [ResetMFA](https://techdocs.akamai.com/iam-api/reference/put-ui-identity-reset-additional-authentication) + * Added API Client Credentials methods: + * [CreateYourCredential](https://techdocs.akamai.com/iam-api/reference/post-self-credentials) and [CreateCredential](https://techdocs.akamai.com/iam-api/reference/post-client-credentials) + * [GetYourCredential](https://techdocs.akamai.com/iam-api/reference/get-self-credential) and [GetCredential](https://techdocs.akamai.com/iam-api/reference/get-client-credential) + * [UpdateYourCredential](https://techdocs.akamai.com/iam-api/reference/put-self-credential) and [UpdateCredential](https://techdocs.akamai.com/iam-api/reference/put-client-credential) + * [DeleteYourCredential](https://techdocs.akamai.com/iam-api/reference/delete-self-credential) and [DeleteCredential](https://techdocs.akamai.com/iam-api/reference/delete-client-credential) + * [ListYourCredentials](https://techdocs.akamai.com/iam-api/reference/get-self-credentials) and [ListCredentials](https://techdocs.akamai.com/iam-api/reference/get-client-credentials) + * [DeactivateYourCredential](https://techdocs.akamai.com/iam-api/reference/post-self-credential-deactivate) and [DeactivateCredential](https://techdocs.akamai.com/iam-api/reference/post-client-credential-deactivate) + * [DeactivateYourCredentials](https://techdocs.akamai.com/iam-api/reference/post-self-credentials-deactivate) and [DeactivateCredentials](https://techdocs.akamai.com/iam-api/reference/post-client-credentials-deactivate) + * Added the `UserStatus` and `AccountID` parameters to the `User` structure. + * Added the [GetPasswordPolicy](https://techdocs.akamai.com/iam-api/reference/get-common-password-policy) method to get a password policy for an account. + * Added Helper APIs + * [ListAllowedCPCodes](https://techdocs.akamai.com/iam-api/reference/post-api-clients-users-allowed-cpcodes) + * [ListAuthorizedUsers](https://techdocs.akamai.com/iam-api/reference/get-api-clients-users) + * [ListAllowedAPIs](https://techdocs.akamai.com/iam-api/reference/get-api-clients-users-allowed-apis) + * [ListAccessibleGroups](https://techdocs.akamai.com/iam-api/reference/get-api-clients-users-group-access) + * Added new methods: + * [ListUsersForProperty](https://techdocs.akamai.com/iam-api/reference/get-property-users) + * [BlockUsers](https://techdocs.akamai.com/iam-api/reference/put-property-users-block) + * [DisableIPAllowlist](https://techdocs.akamai.com/iam-api/reference/post-allowlist-disable) + * [EnableIPAllowlist](https://techdocs.akamai.com/iam-api/reference/post-allowlist-enable) + * [GetIPAllowlistStatus](https://techdocs.akamai.com/iam-api/reference/get-allowlist-status) + * `ListAccountSwitchKeys` based on [ListAccountSwitchKeys](https://techdocs.akamai.com/iam-api/reference/get-client-account-switch-keys) and [ListYourAccountSwitchKeys](https://techdocs.akamai.com/iam-api/reference/get-self-account-switch-keys) + * `LockAPIClient` based on [LockAPIClient](https://techdocs.akamai.com/iam-api/reference/put-lock-api-client) and [LockYourAPIClient](https://techdocs.akamai.com/iam-api/reference/put-lock-api-client-self) + * [UnlockAPIClient](https://techdocs.akamai.com/iam-api/reference/put-unlock-api-client) + * [ListAPIClients](https://techdocs.akamai.com/iam-api/reference/get-api-clients) + * [CreateAPIClient](https://techdocs.akamai.com/iam-api/reference/post-api-clients) + * `GetAPIClient` based on [GetAPIClient](https://techdocs.akamai.com/iam-api/reference/get-api-client) and [GetYourAPIClient](https://techdocs.akamai.com/iam-api/reference/get-api-client-self) + * `UpdateAPIClient` based on [UpdateAPIClient](https://techdocs.akamai.com/iam-api/reference/put-api-clients) and [UpdateYourAPIClient](https://techdocs.akamai.com/iam-api/reference/put-api-clients-self) + * `DeleteAPIClient` based on [DeleteAPIClient](https://techdocs.akamai.com/iam-api/reference/delete-api-client) and [DeleteYourAPIClient](https://techdocs.akamai.com/iam-api/reference/delete-api-client-self) + * [ListCIDRBlocks](https://techdocs.akamai.com/iam-api/reference/get-allowlist) + * [CreateCIDRBlock](https://techdocs.akamai.com/iam-api/reference/post-allowlist) + * [GetCIDRBlock](https://techdocs.akamai.com/iam-api/reference/get-allowlist-cidrblockid) + * [UpdateCIDRBlock](https://techdocs.akamai.com/iam-api/reference/put-allowlist-cidrblockid) + * [DeleteCIDRBlock](https://techdocs.akamai.com/iam-api/reference/delete-allowlist-cidrblockid) + * [ValidateCIDRBlock](https://techdocs.akamai.com/iam-api/reference/get-allowlist-validate) ## 8.4.0 (Aug 22, 2024) @@ -6,7 +298,7 @@ * APPSEC * Added field `ClientLists` to `RuleConditions` and `AttackGroupConditions` - * The `RequestBodyInspectionLimitOverride` field has been added in the following structures: + * The `RequestBodyInspectionLimitOverride` field has been added in these structures: * `GetAdvancedSettingsRequestBodyResponse`, * `UpdateAdvancedSettingsRequestBodyRequest`, * `UpdateAdvancedSettingsRequestBodyResponse`, @@ -69,7 +361,7 @@ #### Deprecations -* Deprecated the following functions in the `tools` package. Use `ptr.To` instead. +* Deprecated these functions in the `tools` package. Use `ptr.To` instead. * `BoolPtr` * `IntPtr` * `Int64Ptr` @@ -95,7 +387,7 @@ #### BUG FIXES: * APPSEC - * The `Override` field in the following structs has been updated from a pointer to a value type within the `AdvancedSettingsAttackPayloadLogging` interface: + * The `Override` field in these structs has been updated from a pointer to a value type within the `AdvancedSettingsAttackPayloadLogging` interface: * `GetAdvancedSettingsAttackPayloadLoggingResponse`, * `UpdateAdvancedSettingsAttackPayloadLoggingResponse`, * `RemoveAdvancedSettingsAttackPayloadLoggingRequest`, @@ -127,12 +419,12 @@ * Split request and response structures for create and update enrollment operations * DNS - * Renamed following structs: + * Renamed these structs: * `RecordsetQueryArgs` into `RecordSetQueryArgs` * `Recordsets` into `RecordSets` * `Recordset` into `RecordSet` * `MetadataH` into `Metadata` - * Renamed following fields: + * Renamed these fields: * `GroupId` into `GroupID` in `ListGroupRequest` * `Recordsets` into `RecordSets` in `RecordSetResponse` * `ContractIds` into `ContractIDs` in `TSIGQueryString` @@ -141,7 +433,7 @@ * `VersionId` into `VersionID` in `ZoneResponse` * `RequestId` into `RequestID` in `BulkZonesResponse`, `BulkStatusResponse`, `BulkCreateResultResponse` and `BulkDeleteResultResponse` * Renamed `RecordSets` interface into `Recordsets` - * Renamed following methods: + * Renamed these methods: * `ListTsigKeys` into `ListTSIGKeys` * `GetTsigKeyZones` into `GetTSIGKeyZones` * `GetTsigKeyAliases` into `GetTSIGKeyAliases` @@ -152,7 +444,7 @@ * `GetRecordsets` into `GetRecordSets` * `CreateRecordsets` into `CreateRecordSets` * `UpdateRecordsets` into `UpdateRecordSets` - * Deleted following methods: + * Deleted these methods: * `NewAuthorityResponse` * `NewChangeListResponse` * `NewRecordBody` @@ -163,13 +455,13 @@ * `NewZoneQueryString` * `NewZoneResponse` * `RecordToMap` - * Unexported following methods: + * Unexported these methods: * `FullIPv6` * `PadCoordinates` * `ValidateZone` * GTM - * Renamed following structs: + * Renamed these structs: * `AsAssignment` into `ASAssignment` * `AsMap` into `ASMap` * `AsMapList` into `ASMapList` @@ -179,7 +471,7 @@ * `CidrMapResponse` into `CIDRMapResponse` * `AsMapResponse` into `ASMapResponse` * `HttpHeader` into `HTTPHeader` - * Renamed following fields: + * Renamed these fields: * `AsNumbers` into `ASNumbers` in `ASAssignment` * `AsMapItems` into `ASMapItems` in `ASMapList` * `CidrMapItems` into `CIDRMapItems` in `CIDRMapList` @@ -200,7 +492,7 @@ * `Ipv6` into `IPv6` in `Property` * `BackupIp` into `BackupIP` in `Property` * Renamed `CidrMaps` interface into `CIDRMaps` - * Renamed following methods: + * Renamed these methods: * `ListAsMaps` into `ListASMaps` * `GetAsMap` into `GetASMap` * `CreateAsMap` into `CreateASMap` @@ -211,7 +503,7 @@ * `CreateCidrMap` into `CreateCIDRMap` * `DeleteCidrMap` into `DeleteCIDRMap` * `UpdateCidrMap` into `UpdateCIDRMap` - * Deleted following methods: + * Deleted these methods: * `NewASAssignment` * `NewAsMap` * `NewCidrAssignment` @@ -422,7 +714,7 @@ ### Deprecations * Challenge Interceptions Rules has been deprecated -* Deprecate the following interfaces used to maintain individual policy protections: +* Deprecate these interfaces used to maintain individual policy protections: * `ApiConstraintsProtection` * `IPGeoProtection` * `MalwareProtection` @@ -500,7 +792,7 @@ ### FEATURES/ENHANCEMENTS: * APPSEC - * Added following BotManager fields to GetExportConfigurationResponse + * Added these BotManager fields to GetExportConfigurationResponse * BotManagement * CustomBotCategories * CustomDefinedBots @@ -526,7 +818,7 @@ * [GetAvailableBehaviors](https://techdocs.akamai.com/property-mgr/reference/get-available-behaviors) * CPS - * Update `Accept` header to the latest schema `application/vnd.akamai.cps.enrollment.v11+json` for the following endpoints: + * Update `Accept` header to the latest schema `application/vnd.akamai.cps.enrollment.v11+json` for these endpoints: * [ListEnrollments](https://techdocs.akamai.com/cps/reference/get-enrollments) * [GetEnrollment](https://techdocs.akamai.com/cps/reference/get-enrollment) @@ -638,7 +930,7 @@ * MalwarePolicyAction * MalwareProtection * Add GetRuleRecommendations method to TuningRecommendations interface - * Add deprecation notes for the following: + * Add deprecation notes for these: * methods: * GetIPGeoProtections * GetNetworkLayerProtections @@ -874,7 +1166,7 @@ #### BREAKING CHANGES: * APPSEC - * The following have been removed, togther with their unit tests and test data: + * The following have been removed, together with their unit tests and test data: * pkg/appsec/attack_group_action.go * pkg/appsec/attack_group_condition_exception.go * pkg/appsec/eval_rule_action.go @@ -918,7 +1210,7 @@ ## 2.3.0 (Mar 15, 2021) Network Lists -Add support for the following operations in the Network Lists API v2: +Add support for the these operations in the Network Lists API v2: * Create a network list * Update an existing network list diff --git a/Makefile b/Makefile index 9911609a..b24fff07 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,13 @@ M = $(shell echo ">") clean-tools: @rm -rf $(BIN)/go* +# Until v0.25.0 is not fixed, we have to use previous version. To install it, we must enable module aware mode. +GOIMPORTS = $(BIN)/goimports +GOIMPORTS_VERSION = v0.24.0 +# Rule to install goimports with version pinning +$(GOIMPORTS): | $(BIN) ; $(info $(M) Installing goimports $(GOIMPORTS_VERSION)...) + $Q env GO111MODULE=on GOBIN=$(BIN) $(GO) install golang.org/x/tools/cmd/goimports@$(GOIMPORTS_VERSION) + $(BIN): @mkdir -p $@ $(BIN)/%: | $(BIN) ; $(info $(M) Building $(PACKAGE)...) @@ -34,9 +41,6 @@ $(BIN)/gocov-xml: PACKAGE=github.com/AlekSi/gocov-xml GOJUNITREPORT = $(BIN)/go-junit-report $(BIN)/go-junit-report: PACKAGE=github.com/jstemmer/go-junit-report -GOIMPORTS = $(BIN)/goimports -$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports - GOLANGCILINT = $(BIN)/golangci-lint $(BIN)/golangci-lint: ; $(info $(M) Installing golangci-lint...) @ $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(BIN) $(GOLANGCI_LINT_VERSION) diff --git a/README.md b/README.md index 1dd3e2fa..814c4d22 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Akamai OPEN EdgeGrid for GoLang v8 +# Akamai OPEN EdgeGrid for GoLang v9 ![Build Status](https://github.com/akamai/akamaiOPEN-edgegrid-golang/actions/workflows/checks.yml/badge.svg) -[![Go Report Card](https://goreportcard.com/badge/github.com/akamai/AkamaiOPEN-edgegrid-golang/v8)](https://goreportcard.com/report/github.com/akamai/AkamaiOPEN-edgegrid-golang/v8) +[![Go Report Card](https://goreportcard.com/badge/github.com/akamai/AkamaiOPEN-edgegrid-golang/v9)](https://goreportcard.com/report/github.com/akamai/AkamaiOPEN-edgegrid-golang/v9) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/akamai/akamaiOPEN-edgegrid-golang) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![GoDoc](https://pkg.go.dev/badge/github.com/akamai/akamaiOPEN-edgegrid-golang?utm_source=godoc)](https://pkg.go.dev/github.com/akamai/AkamaiOPEN-edgegrid-golang/v8) +[![GoDoc](https://pkg.go.dev/badge/github.com/akamai/akamaiOPEN-edgegrid-golang?utm_source=godoc)](https://pkg.go.dev/github.com/akamai/AkamaiOPEN-edgegrid-golang/v9) This module is presently in active development and provides Akamai REST API support for the Akamai Terraform Provider. @@ -12,7 +12,7 @@ This module is presently in active development and provides Akamai REST API supp This module is not backward compatible with the version `v1`. -Originally branch `master` was representing version `v1`. Now it is representing the latest version `v8` and +Originally branch `master` was representing version `v1`. Now it is representing the latest version `v9` and version `v1` was moved to dedicated `v1` branch. @@ -23,6 +23,6 @@ The packages of library can be imported alongside the `v1` library versions with ``` import ( papiv1 "github.com/akamai/AkamaiOPEN-edgegrid-golang/papi-v1" - papi "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/papi" + papi "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/papi" ) ``` diff --git a/go.mod b/go.mod index a4b26851..af7a1d2b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/akamai/AkamaiOPEN-edgegrid-golang/v8 +module github.com/akamai/AkamaiOPEN-edgegrid-golang/v9 go 1.21 diff --git a/internal/test/test.go b/internal/test/test.go index 7c8210fb..f091865f 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -9,7 +9,10 @@ import ( ) // NewTimeFromString returns a time value parsed from a string -// in the RFC3339Nano format +// in the RFC3339Nano format. Note that it cuts off trailing zeros in the milliseconds part, which +// might cause issues in IAM endpoints which do not accept the time format without the milliseconds part. +// +// Example: if "2025-10-11T23:06:59.000Z" is used, the actual value that will be sent is "2025-10-11T23:06:59Z". func NewTimeFromString(t *testing.T, s string) time.Time { parsedTime, err := time.Parse(time.RFC3339Nano, s) require.NoError(t, err) diff --git a/pkg/appsec/activations_test.go b/pkg/appsec/activations_test.go index a6bdb983..b2a3bb8f 100644 --- a/pkg/appsec/activations_test.go +++ b/pkg/appsec/activations_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_attack_payload_logging.go b/pkg/appsec/advanced_settings_attack_payload_logging.go index 539bfb22..b72bad52 100644 --- a/pkg/appsec/advanced_settings_attack_payload_logging.go +++ b/pkg/appsec/advanced_settings_attack_payload_logging.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/appsec/advanced_settings_attack_payload_logging_test.go b/pkg/appsec/advanced_settings_attack_payload_logging_test.go index d2aabdb9..dda9664b 100644 --- a/pkg/appsec/advanced_settings_attack_payload_logging_test.go +++ b/pkg/appsec/advanced_settings_attack_payload_logging_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_evasive_path_match_test.go b/pkg/appsec/advanced_settings_evasive_path_match_test.go index e4aea961..a0fc8cd4 100644 --- a/pkg/appsec/advanced_settings_evasive_path_match_test.go +++ b/pkg/appsec/advanced_settings_evasive_path_match_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_logging_test.go b/pkg/appsec/advanced_settings_logging_test.go index f4c6570d..81d27ca5 100644 --- a/pkg/appsec/advanced_settings_logging_test.go +++ b/pkg/appsec/advanced_settings_logging_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_pii_learning.go b/pkg/appsec/advanced_settings_pii_learning.go index c6649efa..f4b9e5f7 100644 --- a/pkg/appsec/advanced_settings_pii_learning.go +++ b/pkg/appsec/advanced_settings_pii_learning.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/appsec/advanced_settings_pii_learning_test.go b/pkg/appsec/advanced_settings_pii_learning_test.go index 4d80ab4e..6bf2cb07 100644 --- a/pkg/appsec/advanced_settings_pii_learning_test.go +++ b/pkg/appsec/advanced_settings_pii_learning_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_pragma_test.go b/pkg/appsec/advanced_settings_pragma_test.go index 6826a8d8..428778bc 100644 --- a/pkg/appsec/advanced_settings_pragma_test.go +++ b/pkg/appsec/advanced_settings_pragma_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_prefetch_test.go b/pkg/appsec/advanced_settings_prefetch_test.go index 9ad00a7f..d961084a 100644 --- a/pkg/appsec/advanced_settings_prefetch_test.go +++ b/pkg/appsec/advanced_settings_prefetch_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/advanced_settings_request_body.go b/pkg/appsec/advanced_settings_request_body.go index 0228c5b2..68be4b02 100644 --- a/pkg/appsec/advanced_settings_request_body.go +++ b/pkg/appsec/advanced_settings_request_body.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/appsec/advanced_settings_request_body_test.go b/pkg/appsec/advanced_settings_request_body_test.go index e85eba2c..4eea315d 100644 --- a/pkg/appsec/advanced_settings_request_body_test.go +++ b/pkg/appsec/advanced_settings_request_body_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/api_constraints_protection_test.go b/pkg/appsec/api_constraints_protection_test.go index 1fdca331..90dbf797 100644 --- a/pkg/appsec/api_constraints_protection_test.go +++ b/pkg/appsec/api_constraints_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/api_endpoints_test.go b/pkg/appsec/api_endpoints_test.go index e86c8434..fa4ce5a8 100644 --- a/pkg/appsec/api_endpoints_test.go +++ b/pkg/appsec/api_endpoints_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/api_hostname_coverage_match_targets_test.go b/pkg/appsec/api_hostname_coverage_match_targets_test.go index 75745d47..dd9642e0 100644 --- a/pkg/appsec/api_hostname_coverage_match_targets_test.go +++ b/pkg/appsec/api_hostname_coverage_match_targets_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/api_hostname_coverage_overlapping_test.go b/pkg/appsec/api_hostname_coverage_overlapping_test.go index e7901175..fb574831 100644 --- a/pkg/appsec/api_hostname_coverage_overlapping_test.go +++ b/pkg/appsec/api_hostname_coverage_overlapping_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/api_hostname_coverage_test.go b/pkg/appsec/api_hostname_coverage_test.go index ef345e44..2ffb92b9 100644 --- a/pkg/appsec/api_hostname_coverage_test.go +++ b/pkg/appsec/api_hostname_coverage_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/api_request_constraints_test.go b/pkg/appsec/api_request_constraints_test.go index b39feae6..416326ba 100644 --- a/pkg/appsec/api_request_constraints_test.go +++ b/pkg/appsec/api_request_constraints_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/appsec.go b/pkg/appsec/appsec.go index cf935b83..ae94d2f0 100644 --- a/pkg/appsec/appsec.go +++ b/pkg/appsec/appsec.go @@ -4,7 +4,7 @@ package appsec import ( "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( diff --git a/pkg/appsec/appsec_test.go b/pkg/appsec/appsec_test.go index 268ab76b..1058dbcb 100644 --- a/pkg/appsec/appsec_test.go +++ b/pkg/appsec/appsec_test.go @@ -12,8 +12,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/attack_group_test.go b/pkg/appsec/attack_group_test.go index 7c46d2b3..00592b6d 100644 --- a/pkg/appsec/attack_group_test.go +++ b/pkg/appsec/attack_group_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/configuration_clone_test.go b/pkg/appsec/configuration_clone_test.go index 04e8eeb9..faaad4d0 100644 --- a/pkg/appsec/configuration_clone_test.go +++ b/pkg/appsec/configuration_clone_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/configuration_test.go b/pkg/appsec/configuration_test.go index c3571d37..1a76acab 100644 --- a/pkg/appsec/configuration_test.go +++ b/pkg/appsec/configuration_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/configuration_version_clone_test.go b/pkg/appsec/configuration_version_clone_test.go index a42e60dd..570564e4 100644 --- a/pkg/appsec/configuration_version_clone_test.go +++ b/pkg/appsec/configuration_version_clone_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/configuration_version_test.go b/pkg/appsec/configuration_version_test.go index a604f7d3..a5f692a5 100644 --- a/pkg/appsec/configuration_version_test.go +++ b/pkg/appsec/configuration_version_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/custom_deny_test.go b/pkg/appsec/custom_deny_test.go index 2abfadd3..b4927e17 100644 --- a/pkg/appsec/custom_deny_test.go +++ b/pkg/appsec/custom_deny_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/custom_rule_action_test.go b/pkg/appsec/custom_rule_action_test.go index c28b166e..87976a58 100644 --- a/pkg/appsec/custom_rule_action_test.go +++ b/pkg/appsec/custom_rule_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/custom_rule_test.go b/pkg/appsec/custom_rule_test.go index ff2f98ab..647e6d97 100644 --- a/pkg/appsec/custom_rule_test.go +++ b/pkg/appsec/custom_rule_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/errors.go b/pkg/appsec/errors.go index 77bf0f4c..a56849ad 100644 --- a/pkg/appsec/errors.go +++ b/pkg/appsec/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) var ( diff --git a/pkg/appsec/errors_test.go b/pkg/appsec/errors_test.go index 9f5c6f68..1b4b3061 100644 --- a/pkg/appsec/errors_test.go +++ b/pkg/appsec/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/appsec/eval_group_test.go b/pkg/appsec/eval_group_test.go index 19c36874..d9e813c0 100644 --- a/pkg/appsec/eval_group_test.go +++ b/pkg/appsec/eval_group_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/eval_penalty_box_conditions_test.go b/pkg/appsec/eval_penalty_box_conditions_test.go index 59f22b86..bad209ef 100644 --- a/pkg/appsec/eval_penalty_box_conditions_test.go +++ b/pkg/appsec/eval_penalty_box_conditions_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/eval_penalty_box_test.go b/pkg/appsec/eval_penalty_box_test.go index ccb1eb1c..03fc31b9 100644 --- a/pkg/appsec/eval_penalty_box_test.go +++ b/pkg/appsec/eval_penalty_box_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/eval_rule_test.go b/pkg/appsec/eval_rule_test.go index 16267999..512ca28d 100644 --- a/pkg/appsec/eval_rule_test.go +++ b/pkg/appsec/eval_rule_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/eval_test.go b/pkg/appsec/eval_test.go index 2aa50739..26756398 100644 --- a/pkg/appsec/eval_test.go +++ b/pkg/appsec/eval_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/export_configuration.go b/pkg/appsec/export_configuration.go index 0c208542..98cde722 100644 --- a/pkg/appsec/export_configuration.go +++ b/pkg/appsec/export_configuration.go @@ -3,11 +3,16 @@ package appsec import ( "context" "encoding/json" + "errors" "fmt" "net/http" + "net/url" "reflect" "time" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( @@ -32,8 +37,9 @@ type ( // GetExportConfigurationRequest is used to call GetExportConfiguration. GetExportConfigurationRequest struct { - ConfigID int `json:"configId"` - Version int `json:"version"` + ConfigID int `json:"configId"` + Version int `json:"version"` + Source string `json:"source,omitempty"` } // EvaluatingSecurityPolicy is returned from a call to GetExportConfiguration. @@ -59,6 +65,7 @@ type ( Production struct { Status string `json:"status"` } `json:"production"` + TargetProduct string `json:"targetProduct"` CreateDate time.Time `json:"-"` CreatedBy string `json:"createdBy"` SelectedHosts []string `json:"selectedHosts"` @@ -816,6 +823,19 @@ type ( } ) +// Validate validates an GetExportConfigurationRequest struct. +func (v GetExportConfigurationRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Source": validation.Validate(v.Source, validation.In("TF").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'TF' or empty", v.Source))), + }) +} + +var ( + // ErrGetExportConfiguration is returned when ErrGetExportConfiguration fails + ErrGetExportConfiguration = errors.New("get export configuration") +) + // UnmarshalJSON reads a ConditionsValue struct from its data argument. func (c *ConditionsValue) UnmarshalJSON(data []byte) error { var nums interface{} @@ -848,12 +868,22 @@ func (p *appsec) GetExportConfiguration(ctx context.Context, params GetExportCon logger := p.Log(ctx) logger.Debug("GetExportConfiguration") - uri := fmt.Sprintf( - "/appsec/v1/export/configs/%d/versions/%d", - params.ConfigID, - params.Version) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetExportConfiguration, ErrStructValidation, err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + uri, err := url.Parse(fmt.Sprintf("/appsec/v1/export/configs/%d/versions/%d", params.ConfigID, params.Version)) + if err != nil { + return nil, fmt.Errorf("failed to parse url: %s", err) + } + + if params.Source != "" { + q := uri.Query() + q.Add("source", params.Source) + uri.RawQuery = q.Encode() + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("failed to create GetExportConfiguration request: %w", err) } diff --git a/pkg/appsec/export_configuration_test.go b/pkg/appsec/export_configuration_test.go index 5cf0c104..b5fec0ef 100644 --- a/pkg/appsec/export_configuration_test.go +++ b/pkg/appsec/export_configuration_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,19 +34,21 @@ func TestAppSec_ListExportConfiguration(t *testing.T) { params: GetExportConfigurationRequest{ ConfigID: 43253, Version: 15, + Source: "TF", }, headers: http.Header{ "Content-Type": []string{"application/json"}, }, responseStatus: http.StatusOK, responseBody: string(respData), - expectedPath: "/appsec/v1/export/configs/43253/versions/15", + expectedPath: "/appsec/v1/export/configs/43253/versions/15?source=TF", expectedResponse: &result, }, "500 internal server error": { params: GetExportConfigurationRequest{ ConfigID: 43253, Version: 15, + Source: "TF", }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, @@ -57,7 +59,7 @@ func TestAppSec_ListExportConfiguration(t *testing.T) { "detail": "Error fetching propertys", "status": 500 }`, - expectedPath: "/appsec/v1/export/configs/43253/versions/15", + expectedPath: "/appsec/v1/export/configs/43253/versions/15?source=TF", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", diff --git a/pkg/appsec/failover_hostnames_test.go b/pkg/appsec/failover_hostnames_test.go index 328f866b..e33a99a2 100644 --- a/pkg/appsec/failover_hostnames_test.go +++ b/pkg/appsec/failover_hostnames_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/ip_geo_protection_test.go b/pkg/appsec/ip_geo_protection_test.go index bac1fa93..d43038f0 100644 --- a/pkg/appsec/ip_geo_protection_test.go +++ b/pkg/appsec/ip_geo_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/ip_geo_test.go b/pkg/appsec/ip_geo_test.go index a0dea382..18ba7410 100644 --- a/pkg/appsec/ip_geo_test.go +++ b/pkg/appsec/ip_geo_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/malware_policy_action_test.go b/pkg/appsec/malware_policy_action_test.go index 17816941..1dd7ee8c 100644 --- a/pkg/appsec/malware_policy_action_test.go +++ b/pkg/appsec/malware_policy_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/malware_policy_test.go b/pkg/appsec/malware_policy_test.go index 6046dd65..2d722654 100644 --- a/pkg/appsec/malware_policy_test.go +++ b/pkg/appsec/malware_policy_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/malware_protection_test.go b/pkg/appsec/malware_protection_test.go index 21e79d49..d1367b0f 100644 --- a/pkg/appsec/malware_protection_test.go +++ b/pkg/appsec/malware_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/match_target_sequence_test.go b/pkg/appsec/match_target_sequence_test.go index 75e6b023..211a871d 100644 --- a/pkg/appsec/match_target_sequence_test.go +++ b/pkg/appsec/match_target_sequence_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/match_target_test.go b/pkg/appsec/match_target_test.go index 74edbc99..c6979f0a 100644 --- a/pkg/appsec/match_target_test.go +++ b/pkg/appsec/match_target_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/penalty_box_conditions.go b/pkg/appsec/penalty_box_conditions.go index ddb1894c..849d6c3c 100644 --- a/pkg/appsec/penalty_box_conditions.go +++ b/pkg/appsec/penalty_box_conditions.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/appsec/penalty_box_conditions_test.go b/pkg/appsec/penalty_box_conditions_test.go index 94a8ae17..4feb24cf 100644 --- a/pkg/appsec/penalty_box_conditions_test.go +++ b/pkg/appsec/penalty_box_conditions_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/penalty_box_test.go b/pkg/appsec/penalty_box_test.go index b583a338..dbf42aaf 100644 --- a/pkg/appsec/penalty_box_test.go +++ b/pkg/appsec/penalty_box_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/rate_policy_action_test.go b/pkg/appsec/rate_policy_action_test.go index 6ac39d21..f3471fb6 100644 --- a/pkg/appsec/rate_policy_action_test.go +++ b/pkg/appsec/rate_policy_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/rate_policy_test.go b/pkg/appsec/rate_policy_test.go index 1ed7ae9a..b12f835b 100644 --- a/pkg/appsec/rate_policy_test.go +++ b/pkg/appsec/rate_policy_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/rate_protection_test.go b/pkg/appsec/rate_protection_test.go index fab78eb0..cd72d5b6 100644 --- a/pkg/appsec/rate_protection_test.go +++ b/pkg/appsec/rate_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/reputation_analysis_test.go b/pkg/appsec/reputation_analysis_test.go index 37ba4902..0a1d014e 100644 --- a/pkg/appsec/reputation_analysis_test.go +++ b/pkg/appsec/reputation_analysis_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/reputation_profile_action_test.go b/pkg/appsec/reputation_profile_action_test.go index 985a3c6a..26115e57 100644 --- a/pkg/appsec/reputation_profile_action_test.go +++ b/pkg/appsec/reputation_profile_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/reputation_profile_test.go b/pkg/appsec/reputation_profile_test.go index 0369cfa0..7099d6ae 100644 --- a/pkg/appsec/reputation_profile_test.go +++ b/pkg/appsec/reputation_profile_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/reputation_protection_test.go b/pkg/appsec/reputation_protection_test.go index 49624695..3dbda7ec 100644 --- a/pkg/appsec/reputation_protection_test.go +++ b/pkg/appsec/reputation_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/rule_test.go b/pkg/appsec/rule_test.go index 77a392b4..9d146aae 100644 --- a/pkg/appsec/rule_test.go +++ b/pkg/appsec/rule_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/rule_upgrade_test.go b/pkg/appsec/rule_upgrade_test.go index 10d4272f..ae26399e 100644 --- a/pkg/appsec/rule_upgrade_test.go +++ b/pkg/appsec/rule_upgrade_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/security_policy.go b/pkg/appsec/security_policy.go index 983a3677..56546423 100644 --- a/pkg/appsec/security_policy.go +++ b/pkg/appsec/security_policy.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/appsec/security_policy_clone_test.go b/pkg/appsec/security_policy_clone_test.go index a2641353..955cca37 100644 --- a/pkg/appsec/security_policy_clone_test.go +++ b/pkg/appsec/security_policy_clone_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/security_policy_protections_test.go b/pkg/appsec/security_policy_protections_test.go index 60c0ca6d..375d9fbe 100644 --- a/pkg/appsec/security_policy_protections_test.go +++ b/pkg/appsec/security_policy_protections_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/security_policy_test.go b/pkg/appsec/security_policy_test.go index be104ed0..b8a30e19 100644 --- a/pkg/appsec/security_policy_test.go +++ b/pkg/appsec/security_policy_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/selectable_hostnames_test.go b/pkg/appsec/selectable_hostnames_test.go index 1f10fe64..598345ca 100644 --- a/pkg/appsec/selectable_hostnames_test.go +++ b/pkg/appsec/selectable_hostnames_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/selected_hostname_test.go b/pkg/appsec/selected_hostname_test.go index cb16f4fa..bfdb5a64 100644 --- a/pkg/appsec/selected_hostname_test.go +++ b/pkg/appsec/selected_hostname_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/siem_settings.go b/pkg/appsec/siem_settings.go index 01cf7c1b..ec6b4eb6 100644 --- a/pkg/appsec/siem_settings.go +++ b/pkg/appsec/siem_settings.go @@ -36,11 +36,12 @@ type ( // GetSiemSettingsResponse is returned from a call to GetSiemSettings. GetSiemSettingsResponse struct { - EnableForAllPolicies bool `json:"enableForAllPolicies"` - EnableSiem bool `json:"enableSiem"` - EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` - SiemDefinitionID int `json:"siemDefinitionId"` - FirewallPolicyIds []string `json:"firewallPolicyIds"` + EnableForAllPolicies bool `json:"enableForAllPolicies"` + EnableSiem bool `json:"enableSiem"` + EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` + SiemDefinitionID int `json:"siemDefinitionId"` + FirewallPolicyIds []string `json:"firewallPolicyIds"` + Exceptions []Exception `json:"exceptions"` } // GetSiemSettingRequest is used to retrieve the SIEM settings for a configuration. @@ -49,33 +50,42 @@ type ( Version int `json:"-"` } + // Exception is used to create exceptions list for SIEM events + Exception struct { + Protection string `json:"protection"` + ActionTypes []string `json:"actionTypes"` + } + // GetSiemSettingResponse is returned from a call to GetSiemSettings. GetSiemSettingResponse struct { - EnableForAllPolicies bool `json:"enableForAllPolicies"` - EnableSiem bool `json:"enableSiem"` - EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` - SiemDefinitionID int `json:"siemDefinitionId"` - FirewallPolicyIds []string `json:"firewallPolicyIds"` + EnableForAllPolicies bool `json:"enableForAllPolicies"` + EnableSiem bool `json:"enableSiem"` + EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` + SiemDefinitionID int `json:"siemDefinitionId"` + FirewallPolicyIds []string `json:"firewallPolicyIds"` + Exceptions []Exception `json:"exceptions"` } // UpdateSiemSettingsRequest is used to modify the SIEM settings for a configuration. UpdateSiemSettingsRequest struct { - ConfigID int `json:"-"` - Version int `json:"-"` - EnableForAllPolicies bool `json:"enableForAllPolicies"` - EnableSiem bool `json:"enableSiem"` - EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` - SiemDefinitionID int `json:"siemDefinitionId"` - FirewallPolicyIds []string `json:"firewallPolicyIds"` + ConfigID int `json:"-"` + Version int `json:"-"` + EnableForAllPolicies bool `json:"enableForAllPolicies"` + EnableSiem bool `json:"enableSiem"` + EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` + SiemDefinitionID int `json:"siemDefinitionId"` + FirewallPolicyIds []string `json:"firewallPolicyIds"` + Exceptions []Exception `json:"exceptions,omitempty"` } // UpdateSiemSettingsResponse is returned from a call to UpdateSiemSettings. UpdateSiemSettingsResponse struct { - EnableForAllPolicies bool `json:"enableForAllPolicies"` - EnableSiem bool `json:"enableSiem"` - EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` - SiemDefinitionID int `json:"siemDefinitionId"` - FirewallPolicyIds []string `json:"firewallPolicyIds"` + EnableForAllPolicies bool `json:"enableForAllPolicies"` + EnableSiem bool `json:"enableSiem"` + EnabledBotmanSiemEvents bool `json:"enabledBotmanSiemEvents"` + SiemDefinitionID int `json:"siemDefinitionId"` + FirewallPolicyIds []string `json:"firewallPolicyIds"` + Exceptions []Exception `json:"exceptions"` } // RemoveSiemSettingsRequest is used to remove the SIEM settings for a configuration. @@ -117,6 +127,16 @@ func (v UpdateSiemSettingsRequest) Validate() error { }.Filter() } +// Validate validates an Exception struct. +func (v Exception) Validate() error { + return validation.Errors{ + "Protection": validation.Validate(v.Protection, validation.Required, validation.In("botmanagement", "ipgeo", "rate", "urlProtection", "slowpost", "customrules", "waf", "apirequestconstraints", "clientrep", "malwareprotection", "aprProtection"). + Error(fmt.Sprintf("value '%s' is invalid. Must be one of: 'botmanagement', 'ipgeo', 'rate', 'urlProtection', 'slowpost', 'customrules', 'waf', 'apirequestconstraints', 'clientrep', 'malwareprotection', 'aprProtection'", v.Protection))), + "ActionTypes": validation.Validate(v.Protection, validation.Required, validation.In("alert", "deny", "all_custom", "abort", "allow", "delay", "ignore", "monitor", "slow", "tarpit"). + Error(fmt.Sprintf("value '%v' is invalid. Must be one of: 'alert', 'deny', 'all_custom', 'abort', 'allow', 'delay', 'ignore', 'monitor', 'slow', 'tarpit'", v.ActionTypes))), + }.Filter() +} + // Validate validates a RemoveSiemSettingsRequest. // Deprecated: this method will be removed in a future release. func (v RemoveSiemSettingsRequest) Validate() error { diff --git a/pkg/appsec/siem_settings_test.go b/pkg/appsec/siem_settings_test.go index d834319d..46ae4835 100644 --- a/pkg/appsec/siem_settings_test.go +++ b/pkg/appsec/siem_settings_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -188,8 +188,23 @@ func TestAppSec_UpdateSiemSettings(t *testing.T) { }{ "200 Success": { params: UpdateSiemSettingsRequest{ - ConfigID: 43253, - Version: 15, + ConfigID: 43253, + Version: 15, + EnableSiem: true, + Exceptions: []Exception{ + { + ActionTypes: []string{"*"}, + Protection: "botmanagement", + }, + { + ActionTypes: []string{"deny"}, + Protection: "ipgeo", + }, + { + ActionTypes: []string{"alert"}, + Protection: "rate", + }, + }, }, headers: http.Header{ "Content-Type": []string{"application/json;charset=UTF-8"}, diff --git a/pkg/appsec/slow_post_protection_setting_test.go b/pkg/appsec/slow_post_protection_setting_test.go index 0aafcdee..a9e02c47 100644 --- a/pkg/appsec/slow_post_protection_setting_test.go +++ b/pkg/appsec/slow_post_protection_setting_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/slowpost_protection_test.go b/pkg/appsec/slowpost_protection_test.go index 723f45a6..9c85f805 100644 --- a/pkg/appsec/slowpost_protection_test.go +++ b/pkg/appsec/slowpost_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/testdata/TestExportConfiguration/ExportConfiguration.json b/pkg/appsec/testdata/TestExportConfiguration/ExportConfiguration.json index cd8ddfe4..d33fc933 100644 --- a/pkg/appsec/testdata/TestExportConfiguration/ExportConfiguration.json +++ b/pkg/appsec/testdata/TestExportConfiguration/ExportConfiguration.json @@ -9,6 +9,7 @@ "production": { "status": "Inactive" }, + "targetProduct" : "KSD", "createDate": "2020-10-06T18:00:20Z", "createdBy": "akava-terraform", "selectedHosts": [ diff --git a/pkg/appsec/testdata/TestSiemSettings/SiemSettings.json b/pkg/appsec/testdata/TestSiemSettings/SiemSettings.json index 415bc545..a6c8b4f3 100644 --- a/pkg/appsec/testdata/TestSiemSettings/SiemSettings.json +++ b/pkg/appsec/testdata/TestSiemSettings/SiemSettings.json @@ -2,5 +2,24 @@ "enableForAllPolicies": true, "enableSiem": true, "enabledBotmanSiemEvents": false, - "siemDefinitionId": 1 + "siemDefinitionId": 1, + "exceptions":[ + { + "actionTypes": [ + "*" + ], + "protection": "botmanagement" + }, + { + "actionTypes": [ + "alert" + ], + "protection": "ipgeo" + }, + { + "actionTypes": [ + "alert" + ], + "protection": "rate" + }] } \ No newline at end of file diff --git a/pkg/appsec/threat_intel_test.go b/pkg/appsec/threat_intel_test.go index 93f394d3..548114cd 100644 --- a/pkg/appsec/threat_intel_test.go +++ b/pkg/appsec/threat_intel_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/tuning_recommendations_test.go b/pkg/appsec/tuning_recommendations_test.go index 4ff76065..7e83a293 100644 --- a/pkg/appsec/tuning_recommendations_test.go +++ b/pkg/appsec/tuning_recommendations_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/version_notes_test.go b/pkg/appsec/version_notes_test.go index 2f2cf363..4298b900 100644 --- a/pkg/appsec/version_notes_test.go +++ b/pkg/appsec/version_notes_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/waf_mode_test.go b/pkg/appsec/waf_mode_test.go index 72039ec9..03dcdcd0 100644 --- a/pkg/appsec/waf_mode_test.go +++ b/pkg/appsec/waf_mode_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/waf_protection_test.go b/pkg/appsec/waf_protection_test.go index 47e64983..18a4ff69 100644 --- a/pkg/appsec/waf_protection_test.go +++ b/pkg/appsec/waf_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/wap_bypass_network_lists_test.go b/pkg/appsec/wap_bypass_network_lists_test.go index 26475963..3ff0943b 100644 --- a/pkg/appsec/wap_bypass_network_lists_test.go +++ b/pkg/appsec/wap_bypass_network_lists_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/appsec/wap_selected_hostnames_test.go b/pkg/appsec/wap_selected_hostnames_test.go index 14e7d349..05077cdc 100644 --- a/pkg/appsec/wap_selected_hostnames_test.go +++ b/pkg/appsec/wap_selected_hostnames_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/akamai_bot_category_action_test.go b/pkg/botman/akamai_bot_category_action_test.go index 9f2debe8..16090f14 100644 --- a/pkg/botman/akamai_bot_category_action_test.go +++ b/pkg/botman/akamai_bot_category_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/akamai_bot_category_test.go b/pkg/botman/akamai_bot_category_test.go index b202d4fb..f39f0811 100644 --- a/pkg/botman/akamai_bot_category_test.go +++ b/pkg/botman/akamai_bot_category_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/akamai_defined_bot_test.go b/pkg/botman/akamai_defined_bot_test.go index 54eeb483..b728938d 100644 --- a/pkg/botman/akamai_defined_bot_test.go +++ b/pkg/botman/akamai_defined_bot_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/bot_analytics_cookie_test.go b/pkg/botman/bot_analytics_cookie_test.go index 823db04c..c4eb2372 100644 --- a/pkg/botman/bot_analytics_cookie_test.go +++ b/pkg/botman/bot_analytics_cookie_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/bot_analytics_cookie_values_test.go b/pkg/botman/bot_analytics_cookie_values_test.go index c4f08a19..26f43ae1 100644 --- a/pkg/botman/bot_analytics_cookie_values_test.go +++ b/pkg/botman/bot_analytics_cookie_values_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/bot_category_exception_test.go b/pkg/botman/bot_category_exception_test.go index 2b1bbbe1..ebba6474 100644 --- a/pkg/botman/bot_category_exception_test.go +++ b/pkg/botman/bot_category_exception_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/botman/bot_detection_action_test.go b/pkg/botman/bot_detection_action_test.go index ec06a913..abe45b27 100644 --- a/pkg/botman/bot_detection_action_test.go +++ b/pkg/botman/bot_detection_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/bot_detection_test.go b/pkg/botman/bot_detection_test.go index 9e5454e9..363fc9fa 100644 --- a/pkg/botman/bot_detection_test.go +++ b/pkg/botman/bot_detection_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/bot_endpoint_coverage_report_test.go b/pkg/botman/bot_endpoint_coverage_report_test.go index 75c5ef5d..b4c1a3d7 100644 --- a/pkg/botman/bot_endpoint_coverage_report_test.go +++ b/pkg/botman/bot_endpoint_coverage_report_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/bot_management_setting_test.go b/pkg/botman/bot_management_setting_test.go index cd7231f3..3db52706 100644 --- a/pkg/botman/bot_management_setting_test.go +++ b/pkg/botman/bot_management_setting_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/botman/botman.go b/pkg/botman/botman.go index c5a43329..a43bc033 100644 --- a/pkg/botman/botman.go +++ b/pkg/botman/botman.go @@ -4,7 +4,7 @@ package botman import ( "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( diff --git a/pkg/botman/botman_test.go b/pkg/botman/botman_test.go index b30a12ee..b84d3036 100644 --- a/pkg/botman/botman_test.go +++ b/pkg/botman/botman_test.go @@ -12,8 +12,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/challenge_action_test.go b/pkg/botman/challenge_action_test.go index f017d8f2..41a94ebc 100644 --- a/pkg/botman/challenge_action_test.go +++ b/pkg/botman/challenge_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/challenge_injection_rules.go b/pkg/botman/challenge_injection_rules.go index 9d455a5d..019b5785 100644 --- a/pkg/botman/challenge_injection_rules.go +++ b/pkg/botman/challenge_injection_rules.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/botman/challenge_injection_rules_test.go b/pkg/botman/challenge_injection_rules_test.go index 0d4f4743..489d2494 100644 --- a/pkg/botman/challenge_injection_rules_test.go +++ b/pkg/botman/challenge_injection_rules_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/challenge_interception_rules_test.go b/pkg/botman/challenge_interception_rules_test.go index 0f757cd3..cb8b8bc1 100644 --- a/pkg/botman/challenge_interception_rules_test.go +++ b/pkg/botman/challenge_interception_rules_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/client_side_security_test.go b/pkg/botman/client_side_security_test.go index 3fbcc949..6953e6d8 100644 --- a/pkg/botman/client_side_security_test.go +++ b/pkg/botman/client_side_security_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/conditonal_action_test.go b/pkg/botman/conditonal_action_test.go index 0feaeddb..6a143461 100644 --- a/pkg/botman/conditonal_action_test.go +++ b/pkg/botman/conditonal_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/content_protection_javascript_injection_rule_test.go b/pkg/botman/content_protection_javascript_injection_rule_test.go index 3a6fdc7f..7d25d991 100644 --- a/pkg/botman/content_protection_javascript_injection_rule_test.go +++ b/pkg/botman/content_protection_javascript_injection_rule_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/content_protection_rule_sequence_test.go b/pkg/botman/content_protection_rule_sequence_test.go index 92f7c114..bbf3aee6 100644 --- a/pkg/botman/content_protection_rule_sequence_test.go +++ b/pkg/botman/content_protection_rule_sequence_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/content_protection_rule_test.go b/pkg/botman/content_protection_rule_test.go index c7ecaf97..03e14780 100644 --- a/pkg/botman/content_protection_rule_test.go +++ b/pkg/botman/content_protection_rule_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_bot_category_action_test.go b/pkg/botman/custom_bot_category_action_test.go index ec230c50..a2477154 100644 --- a/pkg/botman/custom_bot_category_action_test.go +++ b/pkg/botman/custom_bot_category_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_bot_category_item_sequence.go b/pkg/botman/custom_bot_category_item_sequence.go index 1d18cfc7..fef932d6 100644 --- a/pkg/botman/custom_bot_category_item_sequence.go +++ b/pkg/botman/custom_bot_category_item_sequence.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/botman/custom_bot_category_item_sequence_test.go b/pkg/botman/custom_bot_category_item_sequence_test.go index 0463fba2..a90843b8 100644 --- a/pkg/botman/custom_bot_category_item_sequence_test.go +++ b/pkg/botman/custom_bot_category_item_sequence_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_bot_category_sequence_test.go b/pkg/botman/custom_bot_category_sequence_test.go index c7c7719b..00f7d68e 100644 --- a/pkg/botman/custom_bot_category_sequence_test.go +++ b/pkg/botman/custom_bot_category_sequence_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_bot_category_test.go b/pkg/botman/custom_bot_category_test.go index 20508a48..9187db20 100644 --- a/pkg/botman/custom_bot_category_test.go +++ b/pkg/botman/custom_bot_category_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_client_sequence.go b/pkg/botman/custom_client_sequence.go index 5fbfc96b..1a383453 100644 --- a/pkg/botman/custom_client_sequence.go +++ b/pkg/botman/custom_client_sequence.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/botman/custom_client_sequence_test.go b/pkg/botman/custom_client_sequence_test.go index 6b18b426..d2f5ad11 100644 --- a/pkg/botman/custom_client_sequence_test.go +++ b/pkg/botman/custom_client_sequence_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_client_test.go b/pkg/botman/custom_client_test.go index d5cd2758..b02e3bba 100644 --- a/pkg/botman/custom_client_test.go +++ b/pkg/botman/custom_client_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_code_test.go b/pkg/botman/custom_code_test.go index e0d43ba3..9aef73c2 100644 --- a/pkg/botman/custom_code_test.go +++ b/pkg/botman/custom_code_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_defined_bot_test.go b/pkg/botman/custom_defined_bot_test.go index ce4885cf..827da2ae 100644 --- a/pkg/botman/custom_defined_bot_test.go +++ b/pkg/botman/custom_defined_bot_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/custom_deny_action_test.go b/pkg/botman/custom_deny_action_test.go index 68fcabc2..d396f57c 100644 --- a/pkg/botman/custom_deny_action_test.go +++ b/pkg/botman/custom_deny_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/errors.go b/pkg/botman/errors.go index c2c1a306..2279b6dd 100644 --- a/pkg/botman/errors.go +++ b/pkg/botman/errors.go @@ -8,7 +8,7 @@ import ( "net/http" "strings" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/botman/errors_test.go b/pkg/botman/errors_test.go index 4efa1882..fb856143 100644 --- a/pkg/botman/errors_test.go +++ b/pkg/botman/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" diff --git a/pkg/botman/javascript_injection_test.go b/pkg/botman/javascript_injection_test.go index f7f5f900..4784b343 100644 --- a/pkg/botman/javascript_injection_test.go +++ b/pkg/botman/javascript_injection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/recategorized_akamai_defined_bot_test.go b/pkg/botman/recategorized_akamai_defined_bot_test.go index b51696bc..489655c2 100644 --- a/pkg/botman/recategorized_akamai_defined_bot_test.go +++ b/pkg/botman/recategorized_akamai_defined_bot_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/response_action_test.go b/pkg/botman/response_action_test.go index 47744a23..85609e80 100644 --- a/pkg/botman/response_action_test.go +++ b/pkg/botman/response_action_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/serve_alternate_action_test.go b/pkg/botman/serve_alternate_action_test.go index f9c1ead8..f31e02cd 100644 --- a/pkg/botman/serve_alternate_action_test.go +++ b/pkg/botman/serve_alternate_action_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/transactional_endpoint_protection_test.go b/pkg/botman/transactional_endpoint_protection_test.go index d8120e06..f45fb66c 100644 --- a/pkg/botman/transactional_endpoint_protection_test.go +++ b/pkg/botman/transactional_endpoint_protection_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/botman/transactional_endpoint_test.go b/pkg/botman/transactional_endpoint_test.go index 5541c8f5..b27ba2d2 100644 --- a/pkg/botman/transactional_endpoint_test.go +++ b/pkg/botman/transactional_endpoint_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/clientlists/client_list.go b/pkg/clientlists/client_list.go index bae14f21..f972a790 100644 --- a/pkg/clientlists/client_list.go +++ b/pkg/clientlists/client_list.go @@ -7,44 +7,11 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Lists interface to support creating, retrieving, updating and removing client lists. - Lists interface { - // GetClientLists lists all client lists accessible for an authenticated user - // - // See: https://techdocs.akamai.com/client-lists/reference/get-lists - GetClientLists(ctx context.Context, params GetClientListsRequest) (*GetClientListsResponse, error) - - // GetClientList retrieves client list with specific list id - // - // See: https://techdocs.akamai.com/client-lists/reference/get-list - GetClientList(ctx context.Context, params GetClientListRequest) (*GetClientListResponse, error) - - // CreateClientList creates a new client list - // - // See: https://techdocs.akamai.com/client-lists/reference/post-create-list - CreateClientList(ctx context.Context, params CreateClientListRequest) (*CreateClientListResponse, error) - - // UpdateClientList updates existing client list - // - // See: https://techdocs.akamai.com/client-lists/reference/put-update-list - UpdateClientList(ctx context.Context, params UpdateClientListRequest) (*UpdateClientListResponse, error) - - // UpdateClientListItems updates items/entries of an existing client lists - // - // See: https://techdocs.akamai.com/client-lists/reference/post-update-items - UpdateClientListItems(ctx context.Context, params UpdateClientListItemsRequest) (*UpdateClientListItemsResponse, error) - - // DeleteClientList removes a client list - // - // See: https://techdocs.akamai.com/client-lists/reference/delete-list - DeleteClientList(ctx context.Context, params DeleteClientListRequest) error - } - // ClientListType represents client list type ClientListType string diff --git a/pkg/clientlists/client_list_activation.go b/pkg/clientlists/client_list_activation.go index b29ae558..40f5ddfa 100644 --- a/pkg/clientlists/client_list_activation.go +++ b/pkg/clientlists/client_list_activation.go @@ -5,29 +5,11 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Activation interface to support activating client lists. - Activation interface { - // GetActivation retrieves details of a specified activation ID. - // - // See: https://techdocs.akamai.com/client-lists/reference/get-retrieve-activation-status - GetActivation(ctx context.Context, params GetActivationRequest) (*GetActivationResponse, error) - - // GetActivationStatus retrieves activation status for a client list in a network environment. - // - // See: https://techdocs.akamai.com/client-lists/reference/get-activation-status - GetActivationStatus(ctx context.Context, params GetActivationStatusRequest) (*GetActivationStatusResponse, error) - - // CreateActivation activates a client list - // - // See: https://techdocs.akamai.com/client-lists/reference/post-activate-list - CreateActivation(ctx context.Context, params CreateActivationRequest) (*CreateActivationResponse, error) - } - // ActivationParams contains activation general parameters ActivationParams struct { Action ActivationAction `json:"action"` diff --git a/pkg/clientlists/client_list_activation_test.go b/pkg/clientlists/client_list_activation_test.go index 2c9d807e..aef56b93 100644 --- a/pkg/clientlists/client_list_activation_test.go +++ b/pkg/clientlists/client_list_activation_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/clientlists/client_list_test.go b/pkg/clientlists/client_list_test.go index 373457f8..74353dfc 100644 --- a/pkg/clientlists/client_list_test.go +++ b/pkg/clientlists/client_list_test.go @@ -9,8 +9,8 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -225,8 +225,8 @@ func TestGetClientLists(t *testing.T) { IncludeItems: true, IncludeDeprecated: true, IncludeNetworkList: true, - Page: tools.IntPtr(0), - PageSize: tools.IntPtr(2), + Page: ptr.To(0), + PageSize: ptr.To(2), Sort: []string{"updatedBy:desc", "value:desc"}, }, responseStatus: http.StatusOK, diff --git a/pkg/clientlists/clientlists.go b/pkg/clientlists/clientlists.go index b358c3c2..34a6929e 100644 --- a/pkg/clientlists/clientlists.go +++ b/pkg/clientlists/clientlists.go @@ -4,9 +4,10 @@ package clientlists import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,10 +16,56 @@ var ( ) type ( - // ClientLists is the clientlists api interface + // ClientLists is the API interface for Client Lists ClientLists interface { - Activation - Lists + // Activations + + // GetActivation retrieves details of a specified activation ID. + // + // See: https://techdocs.akamai.com/client-lists/reference/get-retrieve-activation-status + GetActivation(ctx context.Context, params GetActivationRequest) (*GetActivationResponse, error) + + // GetActivationStatus retrieves activation status for a client list in a network environment. + // + // See: https://techdocs.akamai.com/client-lists/reference/get-activation-status + GetActivationStatus(ctx context.Context, params GetActivationStatusRequest) (*GetActivationStatusResponse, error) + + // CreateActivation activates a client list + // + // See: https://techdocs.akamai.com/client-lists/reference/post-activate-list + CreateActivation(ctx context.Context, params CreateActivationRequest) (*CreateActivationResponse, error) + + // Lists + + // GetClientLists lists all client lists accessible for an authenticated user + // + // See: https://techdocs.akamai.com/client-lists/reference/get-lists + GetClientLists(ctx context.Context, params GetClientListsRequest) (*GetClientListsResponse, error) + + // GetClientList retrieves client list with specific list id + // + // See: https://techdocs.akamai.com/client-lists/reference/get-list + GetClientList(ctx context.Context, params GetClientListRequest) (*GetClientListResponse, error) + + // CreateClientList creates a new client list + // + // See: https://techdocs.akamai.com/client-lists/reference/post-create-list + CreateClientList(ctx context.Context, params CreateClientListRequest) (*CreateClientListResponse, error) + + // UpdateClientList updates existing client list + // + // See: https://techdocs.akamai.com/client-lists/reference/put-update-list + UpdateClientList(ctx context.Context, params UpdateClientListRequest) (*UpdateClientListResponse, error) + + // UpdateClientListItems updates items/entries of an existing client lists + // + // See: https://techdocs.akamai.com/client-lists/reference/post-update-items + UpdateClientListItems(ctx context.Context, params UpdateClientListItemsRequest) (*UpdateClientListItemsResponse, error) + + // DeleteClientList removes a client list + // + // See: https://techdocs.akamai.com/client-lists/reference/delete-list + DeleteClientList(ctx context.Context, params DeleteClientListRequest) error } clientlists struct { diff --git a/pkg/clientlists/clientlists_test.go b/pkg/clientlists/clientlists_test.go index 7e36a309..d33361cf 100644 --- a/pkg/clientlists/clientlists_test.go +++ b/pkg/clientlists/clientlists_test.go @@ -12,8 +12,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/clientlists/errors.go b/pkg/clientlists/errors.go index 5095c72e..eade8f4e 100644 --- a/pkg/clientlists/errors.go +++ b/pkg/clientlists/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/clientlists/errors_test.go b/pkg/clientlists/errors_test.go index a5bb704d..5a4fc08f 100644 --- a/pkg/clientlists/errors_test.go +++ b/pkg/clientlists/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" diff --git a/pkg/cloudaccess/access_key.go b/pkg/cloudaccess/access_key.go index 73dd6145..f46d864e 100644 --- a/pkg/cloudaccess/access_key.go +++ b/pkg/cloudaccess/access_key.go @@ -8,7 +8,7 @@ import ( "net/url" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudaccess/access_key_version.go b/pkg/cloudaccess/access_key_version.go index d68b0e16..a796d161 100644 --- a/pkg/cloudaccess/access_key_version.go +++ b/pkg/cloudaccess/access_key_version.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) @@ -28,11 +28,11 @@ type ( // CreateAccessKeyVersionRequest holds parameters for CreateAccessKeyVersion CreateAccessKeyVersionRequest struct { AccessKeyUID int64 - BodyParams CreateAccessKeyVersionBodyParams + Body CreateAccessKeyVersionRequestBody } - // CreateAccessKeyVersionBodyParams holds body parameters for CreateAccessKeyVersion - CreateAccessKeyVersionBodyParams struct { + // CreateAccessKeyVersionRequestBody holds body parameters for CreateAccessKeyVersion + CreateAccessKeyVersionRequestBody struct { CloudAccessKeyID string `json:"cloudAccessKeyId"` CloudSecretAccessKey string `json:"cloudSecretAccessKey"` } @@ -106,12 +106,12 @@ func (r GetAccessKeyVersionStatusRequest) Validate() error { func (r CreateAccessKeyVersionRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "AccessKeyUID": validation.Validate(r.AccessKeyUID, validation.Required), - "BodyParams": validation.Validate(r.BodyParams, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), }) } -// Validate validates CreateAccessKeyVersionBodyParams -func (r CreateAccessKeyVersionBodyParams) Validate() error { +// Validate validates CreateAccessKeyVersionRequestBody +func (r CreateAccessKeyVersionRequestBody) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "CloudAccessKeyID": validation.Validate(r.CloudAccessKeyID, validation.Required), "CloudSecretAccessKey": validation.Validate(r.CloudSecretAccessKey, validation.Required), @@ -197,7 +197,7 @@ func (c *cloudaccess) CreateAccessKeyVersion(ctx context.Context, params CreateA } var result CreateAccessKeyVersionResponse - resp, err := c.Exec(req, &result, params.BodyParams) + resp, err := c.Exec(req, &result, params.Body) if err != nil { return nil, fmt.Errorf("%w: request failed: %w", ErrCreateAccessKeyVersion, err) } diff --git a/pkg/cloudaccess/access_key_version_test.go b/pkg/cloudaccess/access_key_version_test.go index ea73d3c4..cbe0ec5a 100644 --- a/pkg/cloudaccess/access_key_version_test.go +++ b/pkg/cloudaccess/access_key_version_test.go @@ -136,7 +136,7 @@ func TestCreateAccessKeyVersion(t *testing.T) { "202 ACCEPTED": { params: CreateAccessKeyVersionRequest{ AccessKeyUID: 1, - BodyParams: CreateAccessKeyVersionBodyParams{ + Body: CreateAccessKeyVersionRequestBody{ CloudAccessKeyID: "key-1", CloudSecretAccessKey: "secret-1", }, @@ -162,13 +162,13 @@ func TestCreateAccessKeyVersion(t *testing.T) { "missing required params - validation error": { params: CreateAccessKeyVersionRequest{}, withError: func(t *testing.T, err error) { - assert.Equal(t, "create access key version: struct validation: AccessKeyUID: cannot be blank\nBodyParams: CloudAccessKeyID: cannot be blank\nCloudSecretAccessKey: cannot be blank", err.Error()) + assert.Equal(t, "create access key version: struct validation: AccessKeyUID: cannot be blank\nBody: CloudAccessKeyID: cannot be blank\nCloudSecretAccessKey: cannot be blank", err.Error()) }, }, "404 error": { params: CreateAccessKeyVersionRequest{ AccessKeyUID: 1, - BodyParams: CreateAccessKeyVersionBodyParams{ + Body: CreateAccessKeyVersionRequestBody{ CloudAccessKeyID: "key-1", CloudSecretAccessKey: "secret-1", }, @@ -205,7 +205,7 @@ func TestCreateAccessKeyVersion(t *testing.T) { "409 error": { params: CreateAccessKeyVersionRequest{ AccessKeyUID: 1, - BodyParams: CreateAccessKeyVersionBodyParams{ + Body: CreateAccessKeyVersionRequestBody{ CloudAccessKeyID: "key-1", CloudSecretAccessKey: "secret-1", }, diff --git a/pkg/cloudaccess/cloudaccess.go b/pkg/cloudaccess/cloudaccess.go index f4d51d5c..cb207a87 100644 --- a/pkg/cloudaccess/cloudaccess.go +++ b/pkg/cloudaccess/cloudaccess.go @@ -5,7 +5,7 @@ import ( "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( diff --git a/pkg/cloudaccess/cloudaccess_test.go b/pkg/cloudaccess/cloudaccess_test.go index 95cf9bc0..919977d0 100644 --- a/pkg/cloudaccess/cloudaccess_test.go +++ b/pkg/cloudaccess/cloudaccess_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cloudaccess/errors.go b/pkg/cloudaccess/errors.go index f90f14f8..978afba4 100644 --- a/pkg/cloudaccess/errors.go +++ b/pkg/cloudaccess/errors.go @@ -7,7 +7,7 @@ import ( "io" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/cloudaccess/errors_test.go b/pkg/cloudaccess/errors_test.go index 476037e4..75ba4603 100644 --- a/pkg/cloudaccess/errors_test.go +++ b/pkg/cloudaccess/errors_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cloudaccess/properties.go b/pkg/cloudaccess/properties.go index 20fe9558..b189e05d 100644 --- a/pkg/cloudaccess/properties.go +++ b/pkg/cloudaccess/properties.go @@ -7,7 +7,7 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudaccess/properties_test.go b/pkg/cloudaccess/properties_test.go index c5aa1135..cfc48698 100644 --- a/pkg/cloudaccess/properties_test.go +++ b/pkg/cloudaccess/properties_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -68,14 +68,14 @@ func TestLookupProperties(t *testing.T) { PropertyID: "prp_5678", PropertyName: "test-property", ProductionVersion: nil, - StagingVersion: tools.Int64Ptr(1), + StagingVersion: ptr.To(int64(1)), }, { AccessKeyUID: 1234, Version: 1, PropertyID: "prp_6789", PropertyName: "test-property2", - ProductionVersion: tools.Int64Ptr(1), + ProductionVersion: ptr.To(int64(1)), StagingVersion: nil, }, }, @@ -330,14 +330,14 @@ func TestPerformAsyncPropertiesLookup(t *testing.T) { PropertyID: "prp_5678", PropertyName: "test-property", ProductionVersion: nil, - StagingVersion: tools.Int64Ptr(1), + StagingVersion: ptr.To(int64(1)), }, { AccessKeyUID: 1234, Version: 1, PropertyID: "prp_6789", PropertyName: "test-property2", - ProductionVersion: tools.Int64Ptr(1), + ProductionVersion: ptr.To(int64(1)), StagingVersion: nil, }, }, diff --git a/pkg/cloudlets/cloudlets.go b/pkg/cloudlets/cloudlets.go index 407e3894..9466536b 100644 --- a/pkg/cloudlets/cloudlets.go +++ b/pkg/cloudlets/cloudlets.go @@ -2,9 +2,10 @@ package cloudlets import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,13 +16,140 @@ var ( type ( // Cloudlets is the api interface for cloudlets Cloudlets interface { - LoadBalancers - LoadBalancerVersions - LoadBalancerActivations - Policies - PolicyProperties - PolicyVersions - PolicyVersionActivations + //LoadBalancers + + // ListOrigins lists all origins of specified type for the current account. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origins + ListOrigins(context.Context, ListOriginsRequest) ([]OriginResponse, error) + + // GetOrigin gets specific origin by originID. + // This operation is only available for the APPLICATION_LOAD_BALANCER origin type. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin + GetOrigin(context.Context, GetOriginRequest) (*Origin, error) + + // CreateOrigin creates configuration for an origin. + // This operation is only available for the APPLICATION_LOAD_BALANCER origin type. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin + CreateOrigin(context.Context, CreateOriginRequest) (*Origin, error) + + // UpdateOrigin creates configuration for an origin. + // This operation is only available for the APPLICATION_LOAD_BALANCER origin type. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-origin + UpdateOrigin(context.Context, UpdateOriginRequest) (*Origin, error) + + // LoadBalancerVersions + + // CreateLoadBalancerVersion creates load balancer version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin-versions + CreateLoadBalancerVersion(context.Context, CreateLoadBalancerVersionRequest) (*LoadBalancerVersion, error) + + // GetLoadBalancerVersion gets specific load balancer version by originID and version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-version + GetLoadBalancerVersion(context.Context, GetLoadBalancerVersionRequest) (*LoadBalancerVersion, error) + + // UpdateLoadBalancerVersion updates specific load balancer version by originID and version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-origin-version + UpdateLoadBalancerVersion(context.Context, UpdateLoadBalancerVersionRequest) (*LoadBalancerVersion, error) + + // ListLoadBalancerVersions lists all versions of Origin with type APPLICATION_LOAD_BALANCER. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-versions + ListLoadBalancerVersions(context.Context, ListLoadBalancerVersionsRequest) ([]LoadBalancerVersion, error) + + // LoadBalancerActivations + + // ListLoadBalancerActivations fetches activations with the most recent listed first. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-activations + ListLoadBalancerActivations(context.Context, ListLoadBalancerActivationsRequest) ([]LoadBalancerActivation, error) + + // ActivateLoadBalancerVersion activates the load balancing version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin-activations + ActivateLoadBalancerVersion(context.Context, ActivateLoadBalancerVersionRequest) (*LoadBalancerActivation, error) + + // Policies + + // ListPolicies lists policies. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policies + ListPolicies(context.Context, ListPoliciesRequest) ([]Policy, error) + + // GetPolicy gets policy by policyID. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy + GetPolicy(context.Context, GetPolicyRequest) (*Policy, error) + + // CreatePolicy creates policy. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-policy + CreatePolicy(context.Context, CreatePolicyRequest) (*Policy, error) + + // RemovePolicy removes policy. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/delete-policy + RemovePolicy(context.Context, RemovePolicyRequest) error + + // UpdatePolicy updates policy. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-policy + UpdatePolicy(context.Context, UpdatePolicyRequest) (*Policy, error) + + // PolicyProperties + + // GetPolicyProperties gets all the associated properties by the policyID. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-properties + GetPolicyProperties(context.Context, GetPolicyPropertiesRequest) (map[string]PolicyProperty, error) + + // DeletePolicyProperty removes a property from a policy activation associated_properties list. + DeletePolicyProperty(context.Context, DeletePolicyPropertyRequest) error + + // PolicyVersions + + // ListPolicyVersions lists policy versions by policyID. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-versions + ListPolicyVersions(context.Context, ListPolicyVersionsRequest) ([]PolicyVersion, error) + + // GetPolicyVersion gets policy version by policyID and version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-version + GetPolicyVersion(context.Context, GetPolicyVersionRequest) (*PolicyVersion, error) + + // CreatePolicyVersion creates policy version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-policy-versions + CreatePolicyVersion(context.Context, CreatePolicyVersionRequest) (*PolicyVersion, error) + + // DeletePolicyVersion deletes policy version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/delete-policy-version + DeletePolicyVersion(context.Context, DeletePolicyVersionRequest) error + + // UpdatePolicyVersion updates policy version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-policy-version + UpdatePolicyVersion(context.Context, UpdatePolicyVersionRequest) (*PolicyVersion, error) + + // PolicyVersionActivations + + // ListPolicyActivations returns the complete activation history for the selected policy in reverse chronological order. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-activations + ListPolicyActivations(context.Context, ListPolicyActivationsRequest) ([]PolicyActivation, error) + + // ActivatePolicyVersion activates the selected cloudlet policy version. + // + // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-policy-version-activations + ActivatePolicyVersion(context.Context, ActivatePolicyVersionRequest) ([]PolicyActivation, error) } cloudlets struct { diff --git a/pkg/cloudlets/cloudlets_test.go b/pkg/cloudlets/cloudlets_test.go index 341e0394..c02bfcdd 100644 --- a/pkg/cloudlets/cloudlets_test.go +++ b/pkg/cloudlets/cloudlets_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cloudlets/errors.go b/pkg/cloudlets/errors.go index 993c011b..b1d80e7e 100644 --- a/pkg/cloudlets/errors.go +++ b/pkg/cloudlets/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/cloudlets/errors_test.go b/pkg/cloudlets/errors_test.go index 9511d89e..4d69444e 100644 --- a/pkg/cloudlets/errors_test.go +++ b/pkg/cloudlets/errors_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/cloudlets/loadbalancer.go b/pkg/cloudlets/loadbalancer.go index 0f9e0b6f..c59b2ade 100644 --- a/pkg/cloudlets/loadbalancer.go +++ b/pkg/cloudlets/loadbalancer.go @@ -7,38 +7,12 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // LoadBalancers is a cloudlets LoadBalancer API interface. - LoadBalancers interface { - // ListOrigins lists all origins of specified type for the current account. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origins - ListOrigins(context.Context, ListOriginsRequest) ([]OriginResponse, error) - - // GetOrigin gets specific origin by originID. - // This operation is only available for the APPLICATION_LOAD_BALANCER origin type. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin - GetOrigin(context.Context, GetOriginRequest) (*Origin, error) - - // CreateOrigin creates configuration for an origin. - // This operation is only available for the APPLICATION_LOAD_BALANCER origin type. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin - CreateOrigin(context.Context, CreateOriginRequest) (*Origin, error) - - // UpdateOrigin creates configuration for an origin. - // This operation is only available for the APPLICATION_LOAD_BALANCER origin type. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-origin - UpdateOrigin(context.Context, UpdateOriginRequest) (*Origin, error) - } - // OriginResponse is an Origin returned in ListOrigins OriginResponse struct { Hostname string `json:"hostname"` diff --git a/pkg/cloudlets/loadbalancer_activation.go b/pkg/cloudlets/loadbalancer_activation.go index 269f9574..93db45d9 100644 --- a/pkg/cloudlets/loadbalancer_activation.go +++ b/pkg/cloudlets/loadbalancer_activation.go @@ -8,25 +8,12 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // LoadBalancerActivations is a cloudlets LoadBalancer Activation API interface. - LoadBalancerActivations interface { - // ListLoadBalancerActivations fetches activations with the most recent listed first. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-activations - ListLoadBalancerActivations(context.Context, ListLoadBalancerActivationsRequest) ([]LoadBalancerActivation, error) - - // ActivateLoadBalancerVersion activates the load balancing version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin-activations - ActivateLoadBalancerVersion(context.Context, ActivateLoadBalancerVersionRequest) (*LoadBalancerActivation, error) - } - // ListLoadBalancerActivationsRequest contains request parameters for ListLoadBalancerActivations ListLoadBalancerActivationsRequest struct { OriginID string diff --git a/pkg/cloudlets/loadbalancer_activation_test.go b/pkg/cloudlets/loadbalancer_activation_test.go index 54ffc9fb..43a776f1 100644 --- a/pkg/cloudlets/loadbalancer_activation_test.go +++ b/pkg/cloudlets/loadbalancer_activation_test.go @@ -7,8 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -73,8 +72,8 @@ func TestGetLoadBalancerActivations(t *testing.T) { OriginID: "clorigin1", Network: "prod", LatestOnly: true, - PageSize: tools.Int64Ptr(3), - Page: tools.Int64Ptr(1), + PageSize: ptr.To(int64(3)), + Page: ptr.To(int64(1)), }, responseStatus: http.StatusOK, responseBody: ` diff --git a/pkg/cloudlets/loadbalancer_version.go b/pkg/cloudlets/loadbalancer_version.go index 8859b21f..24e1c9a4 100644 --- a/pkg/cloudlets/loadbalancer_version.go +++ b/pkg/cloudlets/loadbalancer_version.go @@ -9,35 +9,12 @@ import ( "strings" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // LoadBalancerVersions is a cloudlets LoadBalancer version API interface. - LoadBalancerVersions interface { - // CreateLoadBalancerVersion creates load balancer version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin-versions - CreateLoadBalancerVersion(context.Context, CreateLoadBalancerVersionRequest) (*LoadBalancerVersion, error) - - // GetLoadBalancerVersion gets specific load balancer version by originID and version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-version - GetLoadBalancerVersion(context.Context, GetLoadBalancerVersionRequest) (*LoadBalancerVersion, error) - - // UpdateLoadBalancerVersion updates specific load balancer version by originID and version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-origin-version - UpdateLoadBalancerVersion(context.Context, UpdateLoadBalancerVersionRequest) (*LoadBalancerVersion, error) - - // ListLoadBalancerVersions lists all versions of Origin with type APPLICATION_LOAD_BALANCER. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-versions - ListLoadBalancerVersions(context.Context, ListLoadBalancerVersionsRequest) ([]LoadBalancerVersion, error) - } - // DataCenter represents the dataCenter field of load balancer version DataCenter struct { City string `json:"city,omitempty"` diff --git a/pkg/cloudlets/loadbalancer_version_test.go b/pkg/cloudlets/loadbalancer_version_test.go index 21914e24..4091b317 100644 --- a/pkg/cloudlets/loadbalancer_version_test.go +++ b/pkg/cloudlets/loadbalancer_version_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/stretchr/testify/require" @@ -36,12 +36,12 @@ func TestCreateLoadBalancerVersion(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, @@ -123,12 +123,12 @@ func TestCreateLoadBalancerVersion(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, @@ -277,22 +277,22 @@ func TestDataCenterValidate(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, "valid data center, minimal set of params": { DataCenter: DataCenter{ - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, "longitude, latitude and percent can be 0": { @@ -302,12 +302,12 @@ func TestDataCenterValidate(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(0), - Longitude: tools.Float64Ptr(0), + Latitude: ptr.To(float64(0)), + Longitude: ptr.To(float64(0)), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(0), + Percent: ptr.To(float64(0)), }, }, "missing all required parameters error": { @@ -438,12 +438,12 @@ func TestGetLoadBalancerVersion(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, @@ -533,12 +533,12 @@ func TestUpdateLoadBalancerVersion(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, @@ -611,12 +611,12 @@ func TestUpdateLoadBalancerVersion(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, @@ -651,12 +651,12 @@ func TestUpdateLoadBalancerVersion(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Description: "Test load balancing configuration.", @@ -811,12 +811,12 @@ func TestListLoadBalancerVersions(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, @@ -842,12 +842,12 @@ func TestListLoadBalancerVersions(t *testing.T) { LivenessHosts: []string{ "clorigin3.www.example.com", }, - Latitude: tools.Float64Ptr(102.78108), - Longitude: tools.Float64Ptr(-116.07064), + Latitude: ptr.To(102.78108), + Longitude: ptr.To(-116.07064), Continent: "NA", Country: "US", OriginID: "clorigin3", - Percent: tools.Float64Ptr(100.0), + Percent: ptr.To(100.0), }, }, Deleted: false, diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 3532f45b..04ecf864 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index b299448f..fe62800b 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -6,10 +6,9 @@ import ( "strings" "testing" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/require" "github.com/tj/assert" - - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" ) func TestUnmarshalJSONMatchRules(t *testing.T) { @@ -574,7 +573,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { &MatchRuleVP{ Type: "vpMatchRule", End: 0, - PassThroughPercent: tools.Float64Ptr(50.50), + PassThroughPercent: ptr.To(50.50), ID: 0, MatchURL: "", Matches: []MatchCriteriaVP{ @@ -640,7 +639,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { &MatchRuleAP{ Type: "apMatchRule", End: 0, - PassThroughPercent: tools.Float64Ptr(50.50), + PassThroughPercent: ptr.To(50.50), ID: 0, MatchURL: "", Matches: []MatchCriteriaAP{ @@ -1029,19 +1028,19 @@ MatchRules[1]: { input: MatchRules{ MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(50.5), + PassThroughPercent: ptr.To(50.5), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(0), + PassThroughPercent: ptr.To(float64(0)), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(100), + PassThroughPercent: ptr.To(float64(100)), }, }, }, @@ -1052,11 +1051,11 @@ MatchRules[1]: { }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(100.1), + PassThroughPercent: ptr.To(100.1), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(-1.1), + PassThroughPercent: ptr.To(-1.1), }, }, withError: ` @@ -1361,19 +1360,19 @@ MatchRules[2]: { input: MatchRules{ MatchRuleVP{ Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), }, MatchRuleVP{ Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(50.5), + PassThroughPercent: ptr.To(50.5), }, MatchRuleVP{ Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(0), + PassThroughPercent: ptr.To(float64(0)), }, MatchRuleVP{ Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(100), + PassThroughPercent: ptr.To(float64(100)), }, }, }, @@ -1384,11 +1383,11 @@ MatchRules[2]: { }, MatchRuleVP{ Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(100.1), + PassThroughPercent: ptr.To(100.1), }, MatchRuleVP{ Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(-1.1), + PassThroughPercent: ptr.To(-1.1), }, }, withError: ` diff --git a/pkg/cloudlets/policy.go b/pkg/cloudlets/policy.go index e161690c..b508638d 100644 --- a/pkg/cloudlets/policy.go +++ b/pkg/cloudlets/policy.go @@ -13,34 +13,6 @@ import ( ) type ( - // Policies is a cloudlets policies API interface. - Policies interface { - // ListPolicies lists policies. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policies - ListPolicies(context.Context, ListPoliciesRequest) ([]Policy, error) - - // GetPolicy gets policy by policyID. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy - GetPolicy(context.Context, GetPolicyRequest) (*Policy, error) - - // CreatePolicy creates policy. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-policy - CreatePolicy(context.Context, CreatePolicyRequest) (*Policy, error) - - // RemovePolicy removes policy. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/delete-policy - RemovePolicy(context.Context, RemovePolicyRequest) error - - // UpdatePolicy updates policy. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-policy - UpdatePolicy(context.Context, UpdatePolicyRequest) (*Policy, error) - } - // Policy is response returned by GetPolicy or UpdatePolicy Policy struct { Location string `json:"location"` diff --git a/pkg/cloudlets/policy_property.go b/pkg/cloudlets/policy_property.go index dd466fa0..3cc89568 100644 --- a/pkg/cloudlets/policy_property.go +++ b/pkg/cloudlets/policy_property.go @@ -7,23 +7,12 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // PolicyProperties interface is a cloudlets API interface for policy associated properties. - PolicyProperties interface { - // GetPolicyProperties gets all the associated properties by the policyID. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-properties - GetPolicyProperties(context.Context, GetPolicyPropertiesRequest) (map[string]PolicyProperty, error) - - // DeletePolicyProperty removes a property from a policy activation associated_properties list. - DeletePolicyProperty(context.Context, DeletePolicyPropertyRequest) error - } - // GetPolicyPropertiesRequest contains request parameters for GetPolicyPropertiesRequest GetPolicyPropertiesRequest struct { PolicyID int64 diff --git a/pkg/cloudlets/policy_test.go b/pkg/cloudlets/policy_test.go index 80233ea1..892c0cc5 100644 --- a/pkg/cloudlets/policy_test.go +++ b/pkg/cloudlets/policy_test.go @@ -7,8 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -154,10 +153,10 @@ func TestListPolicies(t *testing.T) { }, "200 OK with params": { params: ListPoliciesRequest{ - CloudletID: tools.Int64Ptr(2), + CloudletID: ptr.To(int64(2)), IncludeDeleted: true, Offset: 4, - PageSize: tools.IntPtr(5), + PageSize: ptr.To(5), }, responseStatus: http.StatusOK, responseBody: `[]`, diff --git a/pkg/cloudlets/policy_version.go b/pkg/cloudlets/policy_version.go index 42777378..5efb9865 100644 --- a/pkg/cloudlets/policy_version.go +++ b/pkg/cloudlets/policy_version.go @@ -8,40 +8,12 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // PolicyVersions is a cloudlets policy versions API interface. - PolicyVersions interface { - // ListPolicyVersions lists policy versions by policyID. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-versions - ListPolicyVersions(context.Context, ListPolicyVersionsRequest) ([]PolicyVersion, error) - - // GetPolicyVersion gets policy version by policyID and version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-version - GetPolicyVersion(context.Context, GetPolicyVersionRequest) (*PolicyVersion, error) - - // CreatePolicyVersion creates policy version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-policy-versions - CreatePolicyVersion(context.Context, CreatePolicyVersionRequest) (*PolicyVersion, error) - - // DeletePolicyVersion deletes policy version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/delete-policy-version - DeletePolicyVersion(context.Context, DeletePolicyVersionRequest) error - - // UpdatePolicyVersion updates policy version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/put-policy-version - UpdatePolicyVersion(context.Context, UpdatePolicyVersionRequest) (*PolicyVersion, error) - } - // PolicyVersion is response returned by GetPolicyVersion, CreatePolicyVersion or UpdatePolicyVersion PolicyVersion struct { Location string `json:"location"` diff --git a/pkg/cloudlets/policy_version_activation.go b/pkg/cloudlets/policy_version_activation.go index 1b2ce15b..453e9a74 100644 --- a/pkg/cloudlets/policy_version_activation.go +++ b/pkg/cloudlets/policy_version_activation.go @@ -8,25 +8,12 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // PolicyVersionActivations is a cloudlets PolicyVersionActivations API interface. - PolicyVersionActivations interface { - // ListPolicyActivations returns the complete activation history for the selected policy in reverse chronological order. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/get-policy-activations - ListPolicyActivations(context.Context, ListPolicyActivationsRequest) ([]PolicyActivation, error) - - // ActivatePolicyVersion activates the selected cloudlet policy version. - // - // See: https://techdocs.akamai.com/cloudlets/v2/reference/post-policy-version-activations - ActivatePolicyVersion(context.Context, ActivatePolicyVersionRequest) ([]PolicyActivation, error) - } - // ListPolicyActivationsRequest contains the request parameters for ListPolicyActivations ListPolicyActivationsRequest struct { PolicyID int64 diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index 57786a96..710fed12 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -9,8 +9,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -108,7 +107,7 @@ func TestListPolicyVersions(t *testing.T) { IncludeDeleted: true, IncludeActivations: true, Offset: 2, - PageSize: tools.IntPtr(3), + PageSize: ptr.To(3), }, responseStatus: http.StatusOK, responseBody: ` @@ -3327,7 +3326,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "vpMatchRule", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), ID: 0, Matches: []MatchCriteriaVP{ { @@ -3409,7 +3408,7 @@ func TestCreatePolicyVersion(t *testing.T) { ID: 0, MatchURL: "", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), Start: 0, Matches: []MatchCriteriaVP{ { @@ -3437,7 +3436,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "apMatchRule", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(0), + PassThroughPercent: ptr.To(float64(0)), ID: 0, Matches: []MatchCriteriaAP{ { @@ -3520,7 +3519,7 @@ func TestCreatePolicyVersion(t *testing.T) { ID: 0, MatchURL: "", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), Start: 0, Matches: []MatchCriteriaAP{ { @@ -3547,7 +3546,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "apMatchRule", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), ID: 0, Matches: []MatchCriteriaAP{ { @@ -3637,7 +3636,7 @@ func TestCreatePolicyVersion(t *testing.T) { ID: 0, MatchURL: "", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), Start: 0, Matches: []MatchCriteriaAP{ { @@ -3877,7 +3876,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(50.50), + PassThroughPercent: ptr.To(50.50), Name: "rul3", ID: 0, Matches: []MatchCriteriaVP{ @@ -3907,7 +3906,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(50.50), + PassThroughPercent: ptr.To(50.50), Name: "rul3", ID: 0, Matches: []MatchCriteriaAP{ @@ -3990,7 +3989,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "vpMatchRule", - PassThroughPercent: tools.Float64Ptr(101), + PassThroughPercent: ptr.To(float64(101)), Name: "rul3", ID: 0, }, @@ -4009,7 +4008,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(101), + PassThroughPercent: ptr.To(float64(101)), Name: "rul3", ID: 0, }, diff --git a/pkg/cloudlets/v3/cloudlets.go b/pkg/cloudlets/v3/cloudlets.go index c9aa5bd3..7a6bc9ce 100644 --- a/pkg/cloudlets/v3/cloudlets.go +++ b/pkg/cloudlets/v3/cloudlets.go @@ -5,7 +5,7 @@ import ( "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( diff --git a/pkg/cloudlets/v3/cloudlets_test.go b/pkg/cloudlets/v3/cloudlets_test.go index 1075b301..65d24457 100644 --- a/pkg/cloudlets/v3/cloudlets_test.go +++ b/pkg/cloudlets/v3/cloudlets_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cloudlets/v3/errors.go b/pkg/cloudlets/v3/errors.go index 860f7664..49b21d7d 100644 --- a/pkg/cloudlets/v3/errors.go +++ b/pkg/cloudlets/v3/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) // Error is a cloudlets error interface. diff --git a/pkg/cloudlets/v3/errors_test.go b/pkg/cloudlets/v3/errors_test.go index b3e3f28c..ed10ec91 100644 --- a/pkg/cloudlets/v3/errors_test.go +++ b/pkg/cloudlets/v3/errors_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/cloudlets/v3/match_rule.go b/pkg/cloudlets/v3/match_rule.go index 65d12e13..dd62fcf2 100644 --- a/pkg/cloudlets/v3/match_rule.go +++ b/pkg/cloudlets/v3/match_rule.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudlets/v3/match_rule_test.go b/pkg/cloudlets/v3/match_rule_test.go index 68ea0637..e6ffbd7b 100644 --- a/pkg/cloudlets/v3/match_rule_test.go +++ b/pkg/cloudlets/v3/match_rule_test.go @@ -6,10 +6,9 @@ import ( "strings" "testing" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/require" "github.com/tj/assert" - - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" ) func TestUnmarshalJSONMatchRules(t *testing.T) { @@ -351,7 +350,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { &MatchRuleAP{ Type: "apMatchRule", End: 0, - PassThroughPercent: tools.Float64Ptr(50.50), + PassThroughPercent: ptr.To(50.50), ID: 0, MatchURL: "", Matches: []MatchCriteriaAP{ @@ -698,19 +697,19 @@ func TestValidateMatchRules(t *testing.T) { input: MatchRules{ MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(50.5), + PassThroughPercent: ptr.To(50.5), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(0), + PassThroughPercent: ptr.To(float64(0)), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(100), + PassThroughPercent: ptr.To(float64(100)), }, }, }, @@ -721,11 +720,11 @@ func TestValidateMatchRules(t *testing.T) { }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(100.1), + PassThroughPercent: ptr.To(100.1), }, MatchRuleAP{ Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(-1.1), + PassThroughPercent: ptr.To(-1.1), }, }, withError: ` diff --git a/pkg/cloudlets/v3/policy.go b/pkg/cloudlets/v3/policy.go index 98e64516..441808e6 100644 --- a/pkg/cloudlets/v3/policy.go +++ b/pkg/cloudlets/v3/policy.go @@ -10,7 +10,7 @@ import ( "strconv" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) @@ -42,26 +42,26 @@ type ( // UpdatePolicyRequest contains request parameters for UpdatePolicy UpdatePolicyRequest struct { - PolicyID int64 - BodyParams UpdatePolicyBodyParams + PolicyID int64 + Body UpdatePolicyRequestBody } // ClonePolicyRequest contains request parameters for ClonePolicy ClonePolicyRequest struct { - PolicyID int64 - BodyParams ClonePolicyBodyParams + PolicyID int64 + Body ClonePolicyRequestBody } - // ClonePolicyBodyParams contains request body parameters used in ClonePolicy operation + // ClonePolicyRequestBody contains request body parameters used in ClonePolicy operation // GroupID is required only when cloning v2 - ClonePolicyBodyParams struct { + ClonePolicyRequestBody struct { AdditionalVersions []int64 `json:"additionalVersions,omitempty"` GroupID int64 `json:"groupId,omitempty"` NewName string `json:"newName"` } - // UpdatePolicyBodyParams contains request body parameters used in UpdatePolicy operation - UpdatePolicyBodyParams struct { + // UpdatePolicyRequestBody contains request body parameters used in UpdatePolicy operation + UpdatePolicyRequestBody struct { GroupID int64 `json:"groupId"` Description *string `json:"description,omitempty"` } @@ -179,13 +179,13 @@ func (r GetPolicyRequest) Validate() error { // Validate validates UpdatePolicyRequest func (r UpdatePolicyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ - "PolicyID": validation.Validate(r.PolicyID, validation.Required), - "BodyParams": validation.Validate(r.BodyParams, validation.Required), + "PolicyID": validation.Validate(r.PolicyID, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), }) } -// Validate validates UpdatePolicyBodyParams -func (b UpdatePolicyBodyParams) Validate() error { +// Validate validates UpdatePolicyRequestBody +func (b UpdatePolicyRequestBody) Validate() error { return validation.Errors{ "GroupID": validation.Validate(b.GroupID, validation.Required), "Description": validation.Validate(b.Description, validation.Length(0, 255)), @@ -195,13 +195,13 @@ func (b UpdatePolicyBodyParams) Validate() error { // Validate validates ClonePolicyRequest func (r ClonePolicyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ - "PolicyID": validation.Validate(r.PolicyID, validation.Required), - "BodyParams": validation.Validate(r.BodyParams, validation.Required), + "PolicyID": validation.Validate(r.PolicyID, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), }) } // Validate validates ClonePolicyBodyParams -func (b ClonePolicyBodyParams) Validate() error { +func (b ClonePolicyRequestBody) Validate() error { return validation.Errors{ "NewName": validation.Validate(b.NewName, validation.Required, validation.Length(0, 64), validation.Match(regexp.MustCompile("^[a-z_A-Z0-9]+$")). Error(fmt.Sprintf("value '%s' is invalid. Must be of format: ^[a-z_A-Z0-9]+$", b.NewName))), @@ -352,7 +352,7 @@ func (c *cloudlets) UpdatePolicy(ctx context.Context, params UpdatePolicyRequest } var result Policy - resp, err := c.Exec(req, &result, params.BodyParams) + resp, err := c.Exec(req, &result, params.Body) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrUpdatePolicy, err) } @@ -380,7 +380,7 @@ func (c *cloudlets) ClonePolicy(ctx context.Context, params ClonePolicyRequest) } var result Policy - resp, err := c.Exec(req, &result, params.BodyParams) + resp, err := c.Exec(req, &result, params.Body) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrClonePolicy, err) } diff --git a/pkg/cloudlets/v3/policy_activation.go b/pkg/cloudlets/v3/policy_activation.go index d3492886..42c34182 100644 --- a/pkg/cloudlets/v3/policy_activation.go +++ b/pkg/cloudlets/v3/policy_activation.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudlets/v3/policy_activation_test.go b/pkg/cloudlets/v3/policy_activation_test.go index adb39289..941b97a8 100644 --- a/pkg/cloudlets/v3/policy_activation_test.go +++ b/pkg/cloudlets/v3/policy_activation_test.go @@ -8,8 +8,8 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/internal/test" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/ptr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cloudlets/v3/policy_property.go b/pkg/cloudlets/v3/policy_property.go index 19869897..185a36c2 100644 --- a/pkg/cloudlets/v3/policy_property.go +++ b/pkg/cloudlets/v3/policy_property.go @@ -8,7 +8,7 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudlets/v3/policy_test.go b/pkg/cloudlets/v3/policy_test.go index e6ceef4e..ed2b8162 100644 --- a/pkg/cloudlets/v3/policy_test.go +++ b/pkg/cloudlets/v3/policy_test.go @@ -9,9 +9,8 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/internal/test" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/ptr" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -274,7 +273,7 @@ func TestListPolicies(t *testing.T) { }, }, }, - Description: tools.StringPtr("Test"), + Description: ptr.To("Test"), GroupID: 1, ID: 22, Links: []Link{ @@ -567,7 +566,7 @@ func TestCreatePolicy(t *testing.T) { "200 OK - all data": { params: CreatePolicyRequest{ CloudletType: CloudletTypeFR, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), GroupID: 1, Name: "TestName", PolicyType: PolicyTypeShared, @@ -618,7 +617,7 @@ func TestCreatePolicy(t *testing.T) { CreatedBy: "User1", CreatedDate: test.NewTimeFromString(t, "2023-10-23T11:21:19.896Z"), CurrentActivations: CurrentActivations{}, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), GroupID: 1, ID: 11, Links: []Link{ @@ -636,7 +635,7 @@ func TestCreatePolicy(t *testing.T) { "validation errors": { params: CreatePolicyRequest{ CloudletType: "Wrong Cloudlet Type", - Description: tools.StringPtr(strings.Repeat("Too long description", 20)), + Description: ptr.To(strings.Repeat("Too long description", 20)), GroupID: 1, Name: "TestName not match", PolicyType: "Wrong Policy Type", @@ -979,7 +978,7 @@ func TestGetPolicy(t *testing.T) { }, }, }, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), GroupID: 1, ID: 11, Links: []Link{ @@ -1115,7 +1114,7 @@ func TestGetPolicy(t *testing.T) { }, }, }, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), GroupID: 1, ID: 11, Links: []Link{ @@ -1172,7 +1171,7 @@ func TestUpdatePolicy(t *testing.T) { "200 OK - minimal data": { params: UpdatePolicyRequest{ PolicyID: 1, - BodyParams: UpdatePolicyBodyParams{ + Body: UpdatePolicyRequestBody{ GroupID: 11, }, }, @@ -1236,9 +1235,9 @@ func TestUpdatePolicy(t *testing.T) { "200 OK - with description and activations": { params: UpdatePolicyRequest{ PolicyID: 1, - BodyParams: UpdatePolicyBodyParams{ + Body: UpdatePolicyRequestBody{ GroupID: 11, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), }, }, responseStatus: http.StatusOK, @@ -1429,7 +1428,7 @@ func TestUpdatePolicy(t *testing.T) { }, }, }, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), GroupID: 1, ID: 11, Links: []Link{ @@ -1453,9 +1452,9 @@ func TestUpdatePolicy(t *testing.T) { "validation errors - description too long": { params: UpdatePolicyRequest{ PolicyID: 1, - BodyParams: UpdatePolicyBodyParams{ + Body: UpdatePolicyRequestBody{ GroupID: 11, - Description: tools.StringPtr(strings.Repeat("TestDescription", 30)), + Description: ptr.To(strings.Repeat("TestDescription", 30)), }, }, withError: func(t *testing.T, err error) { @@ -1503,7 +1502,7 @@ func TestClonePolicy(t *testing.T) { "200 OK - minimal data": { params: ClonePolicyRequest{ PolicyID: 1, - BodyParams: ClonePolicyBodyParams{ + Body: ClonePolicyRequestBody{ NewName: "NewName", }, }, @@ -1567,7 +1566,7 @@ func TestClonePolicy(t *testing.T) { "200 OK - all data": { params: ClonePolicyRequest{ PolicyID: 1, - BodyParams: ClonePolicyBodyParams{ + Body: ClonePolicyRequestBody{ AdditionalVersions: []int64{1, 2}, GroupID: 11, NewName: "NewName", @@ -1762,7 +1761,7 @@ func TestClonePolicy(t *testing.T) { }, }, }, - Description: tools.StringPtr("Description"), + Description: ptr.To("Description"), GroupID: 1, ID: 11, Links: []Link{ @@ -1786,7 +1785,7 @@ func TestClonePolicy(t *testing.T) { "validation errors - newName too long": { params: ClonePolicyRequest{ PolicyID: 1, - BodyParams: ClonePolicyBodyParams{ + Body: ClonePolicyRequestBody{ GroupID: 11, NewName: strings.Repeat("TestNameTooLong", 10), }, diff --git a/pkg/cloudlets/v3/policy_version.go b/pkg/cloudlets/v3/policy_version.go index eaf7abf9..d1a449f7 100644 --- a/pkg/cloudlets/v3/policy_version.go +++ b/pkg/cloudlets/v3/policy_version.go @@ -8,7 +8,7 @@ import ( "net/url" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) diff --git a/pkg/cloudlets/v3/policy_version_test.go b/pkg/cloudlets/v3/policy_version_test.go index 82aeb101..74bd6b3c 100644 --- a/pkg/cloudlets/v3/policy_version_test.go +++ b/pkg/cloudlets/v3/policy_version_test.go @@ -9,9 +9,8 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/internal/test" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/ptr" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -282,7 +281,7 @@ func TestGetPolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedBy: "jsmith", CreatedDate: test.NewTimeFromString(t, "2023-10-19T09:46:57.395Z"), - Description: tools.StringPtr("test description"), + Description: ptr.To("test description"), ID: 6551191, MatchRules: nil, MatchRulesWarnings: []MatchRulesWarning{}, @@ -403,7 +402,7 @@ func TestGetPolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedBy: "jsmith", CreatedDate: test.NewTimeFromString(t, "2023-10-19T09:46:57.395Z"), - Description: tools.StringPtr("Initial version"), + Description: ptr.To("Initial version"), MatchRules: MatchRules{ &MatchRuleAS{ Type: "asMatchRule", @@ -690,7 +689,7 @@ func TestCreatePolicyVersion(t *testing.T) { "201 created, simple ER": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ - Description: tools.StringPtr("Description for the policy"), + Description: ptr.To("Description for the policy"), }, PolicyID: 276858, }, @@ -710,7 +709,7 @@ func TestCreatePolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedDate: test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z"), CreatedBy: "jsmith", - Description: tools.StringPtr("Description for the policy"), + Description: ptr.To("Description for the policy"), ModifiedBy: "jsmith", ModifiedDate: ptr.To(test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z")), MatchRules: nil, @@ -850,7 +849,7 @@ func TestCreatePolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedDate: test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z"), CreatedBy: "jsmith", - Description: tools.StringPtr("Initial version"), + Description: ptr.To("Initial version"), ModifiedBy: "jsmith", ModifiedDate: ptr.To(test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z")), MatchRules: MatchRules{ @@ -2279,7 +2278,7 @@ func TestCreatePolicyVersion(t *testing.T) { "201 created, complex FR with objectMatchValue - object": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ - Description: tools.StringPtr("New version 1630480693371"), + Description: ptr.To("New version 1630480693371"), MatchRules: MatchRules{ &MatchRuleFR{ ForwardSettings: ForwardSettingsFR{}, @@ -2359,7 +2358,7 @@ func TestCreatePolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedDate: test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z"), CreatedBy: "jsmith", - Description: tools.StringPtr("New version 1630480693371"), + Description: ptr.To("New version 1630480693371"), ModifiedBy: "jsmith", ModifiedDate: ptr.To(test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z")), MatchRules: MatchRules{ @@ -2399,7 +2398,7 @@ func TestCreatePolicyVersion(t *testing.T) { "201 created, complex FR with objectMatchValue - simple": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ - Description: tools.StringPtr("New version 1630480693371"), + Description: ptr.To("New version 1630480693371"), MatchRules: MatchRules{ &MatchRuleFR{ ForwardSettings: ForwardSettingsFR{ @@ -2472,7 +2471,7 @@ func TestCreatePolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedDate: test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z"), CreatedBy: "jsmith", - Description: tools.StringPtr("New version 1630480693371"), + Description: ptr.To("New version 1630480693371"), ModifiedBy: "jsmith", ModifiedDate: ptr.To(test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z")), MatchRules: MatchRules{ @@ -2513,7 +2512,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "apMatchRule", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(0), + PassThroughPercent: ptr.To(float64(0)), ID: 0, Matches: []MatchCriteriaAP{ { @@ -2584,7 +2583,7 @@ func TestCreatePolicyVersion(t *testing.T) { ID: 0, MatchURL: "", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), Start: 0, Matches: []MatchCriteriaAP{ { @@ -2611,7 +2610,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "apMatchRule", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), ID: 0, Matches: []MatchCriteriaAP{ { @@ -2689,7 +2688,7 @@ func TestCreatePolicyVersion(t *testing.T) { ID: 0, MatchURL: "", Name: "rul3", - PassThroughPercent: tools.Float64Ptr(-1), + PassThroughPercent: ptr.To(float64(-1)), Start: 0, Matches: []MatchCriteriaAP{ { @@ -2917,7 +2916,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(50.50), + PassThroughPercent: ptr.To(50.50), Name: "rul3", ID: 0, Matches: []MatchCriteriaAP{ @@ -2983,7 +2982,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "apMatchRule", - PassThroughPercent: tools.Float64Ptr(101), + PassThroughPercent: ptr.To(float64(101)), Name: "rul3", ID: 0, }, @@ -3125,7 +3124,7 @@ func TestUpdatePolicyVersion(t *testing.T) { "201 updated simple ER": { request: UpdatePolicyVersionRequest{ UpdatePolicyVersion: UpdatePolicyVersion{ - Description: tools.StringPtr("Updated description"), + Description: ptr.To("Updated description"), }, PolicyID: 276858, PolicyVersion: 5, @@ -3146,7 +3145,7 @@ func TestUpdatePolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedDate: test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z"), CreatedBy: "jsmith", - Description: tools.StringPtr("Updated description"), + Description: ptr.To("Updated description"), ModifiedBy: "jsmith", ModifiedDate: ptr.To(test.NewTimeFromString(t, "2023-10-19T08:50:47.350Z")), MatchRules: nil, @@ -3157,7 +3156,7 @@ func TestUpdatePolicyVersion(t *testing.T) { "201 updated simple ER with warnings": { request: UpdatePolicyVersionRequest{ UpdatePolicyVersion: UpdatePolicyVersion{ - Description: tools.StringPtr("Updated description"), + Description: ptr.To("Updated description"), MatchRules: MatchRules{ &MatchRuleER{ Name: "er_rule", @@ -3225,7 +3224,7 @@ func TestUpdatePolicyVersion(t *testing.T) { expectedResponse: &PolicyVersion{ CreatedDate: test.NewTimeFromString(t, "2023-10-20T09:21:04.180Z"), CreatedBy: "jsmith", - Description: tools.StringPtr("Updated description"), + Description: ptr.To("Updated description"), ModifiedBy: "jsmith", ModifiedDate: ptr.To(test.NewTimeFromString(t, "2023-10-20T10:32:56.316Z")), ID: 6552305, @@ -3273,7 +3272,7 @@ func TestUpdatePolicyVersion(t *testing.T) { expectedPath: "/cloudlets/v3/policies/0", request: UpdatePolicyVersionRequest{ UpdatePolicyVersion: UpdatePolicyVersion{ - Description: tools.StringPtr(strings.Repeat("A", 256)), + Description: ptr.To(strings.Repeat("A", 256)), }, }, withError: ErrStructValidation, diff --git a/pkg/cloudwrapper/capacity.go b/pkg/cloudwrapper/capacity.go index f7f97024..66a04523 100644 --- a/pkg/cloudwrapper/capacity.go +++ b/pkg/cloudwrapper/capacity.go @@ -9,15 +9,6 @@ import ( ) type ( - // Capacities is a Cloud Wrapper API interface. - Capacities interface { - // ListCapacities fetches capacities available for a given contractId. - // If no contract id is provided, lists all available capacity locations - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-capacity-inventory - ListCapacities(context.Context, ListCapacitiesRequest) (*ListCapacitiesResponse, error) - } - // ListCapacitiesRequest is a request struct ListCapacitiesRequest struct { ContractIDs []string diff --git a/pkg/cloudwrapper/cloudwrapper.go b/pkg/cloudwrapper/cloudwrapper.go index 46b8bbcf..a8f418bf 100644 --- a/pkg/cloudwrapper/cloudwrapper.go +++ b/pkg/cloudwrapper/cloudwrapper.go @@ -2,9 +2,10 @@ package cloudwrapper import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,11 +16,76 @@ var ( type ( // CloudWrapper is the api interface for Cloud Wrapper CloudWrapper interface { - Capacities - Configurations - Locations - MultiCDN - Properties + // Capacities + + // ListCapacities fetches capacities available for a given contractId. + // If no contract id is provided, lists all available capacity locations + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-capacity-inventory + ListCapacities(context.Context, ListCapacitiesRequest) (*ListCapacitiesResponse, error) + + // Configurations + + // GetConfiguration gets a specific Cloud Wrapper configuration + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-configuration + GetConfiguration(context.Context, GetConfigurationRequest) (*Configuration, error) + + // ListConfigurations lists all Cloud Wrapper configurations on your contract + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-configurations + ListConfigurations(context.Context) (*ListConfigurationsResponse, error) + + // CreateConfiguration creates a Cloud Wrapper configuration + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/post-configuration + CreateConfiguration(context.Context, CreateConfigurationRequest) (*Configuration, error) + + // UpdateConfiguration updates a saved or inactive configuration + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/put-configuration + UpdateConfiguration(context.Context, UpdateConfigurationRequest) (*Configuration, error) + + // DeleteConfiguration deletes configuration + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/delete-configuration + DeleteConfiguration(context.Context, DeleteConfigurationRequest) error + + // ActivateConfiguration activates a Cloud Wrapper configuration + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/post-configuration-activations + ActivateConfiguration(context.Context, ActivateConfigurationRequest) error + + // Locations + + // ListLocations returns a list of locations available to distribute Cloud Wrapper capacity + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-locations + ListLocations(context.Context) (*ListLocationResponse, error) + + // MultiCDN + + // ListAuthKeys lists the cdnAuthKeys for a specified contractId and cdnCode + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-auth-keys + ListAuthKeys(context.Context, ListAuthKeysRequest) (*ListAuthKeysResponse, error) + + // ListCDNProviders lists CDN providers + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-providers + ListCDNProviders(context.Context) (*ListCDNProvidersResponse, error) + + // Properties + + // ListProperties lists unused properties + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-properties + ListProperties(context.Context, ListPropertiesRequest) (*ListPropertiesResponse, error) + + // ListOrigins lists property origins + // + // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-origins + ListOrigins(context.Context, ListOriginsRequest) (*ListOriginsResponse, error) } cloudwrapper struct { diff --git a/pkg/cloudwrapper/cloudwrapper_test.go b/pkg/cloudwrapper/cloudwrapper_test.go index 1922f35e..71a52232 100644 --- a/pkg/cloudwrapper/cloudwrapper_test.go +++ b/pkg/cloudwrapper/cloudwrapper_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cloudwrapper/configurations.go b/pkg/cloudwrapper/configurations.go index 6d47041b..6e585e1c 100644 --- a/pkg/cloudwrapper/configurations.go +++ b/pkg/cloudwrapper/configurations.go @@ -8,39 +8,11 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Configurations is a CloudWrapper configurations API interface - Configurations interface { - // GetConfiguration gets a specific Cloud Wrapper configuration - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-configuration - GetConfiguration(context.Context, GetConfigurationRequest) (*Configuration, error) - // ListConfigurations lists all Cloud Wrapper configurations on your contract - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-configurations - ListConfigurations(context.Context) (*ListConfigurationsResponse, error) - // CreateConfiguration creates a Cloud Wrapper configuration - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/post-configuration - CreateConfiguration(context.Context, CreateConfigurationRequest) (*Configuration, error) - // UpdateConfiguration updates a saved or inactive configuration - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/put-configuration - UpdateConfiguration(context.Context, UpdateConfigurationRequest) (*Configuration, error) - // DeleteConfiguration deletes configuration - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/delete-configuration - DeleteConfiguration(context.Context, DeleteConfigurationRequest) error - // ActivateConfiguration activates a Cloud Wrapper configuration - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/post-configuration-activations - ActivateConfiguration(context.Context, ActivateConfigurationRequest) error - } - // GetConfigurationRequest holds parameters for GetConfiguration GetConfigurationRequest struct { ConfigID int64 @@ -49,11 +21,11 @@ type ( // CreateConfigurationRequest holds parameters for CreateConfiguration CreateConfigurationRequest struct { Activate bool - Body CreateConfigurationBody + Body CreateConfigurationRequestBody } - // CreateConfigurationBody holds request body parameters for CreateConfiguration - CreateConfigurationBody struct { + // CreateConfigurationRequestBody holds request body parameters for CreateConfiguration + CreateConfigurationRequestBody struct { CapacityAlertsThreshold *int `json:"capacityAlertsThreshold,omitempty"` Comments string `json:"comments"` ContractID string `json:"contractId"` @@ -69,11 +41,11 @@ type ( UpdateConfigurationRequest struct { ConfigID int64 Activate bool - Body UpdateConfigurationBody + Body UpdateConfigurationRequestBody } - // UpdateConfigurationBody holds request body parameters for UpdateConfiguration - UpdateConfigurationBody struct { + // UpdateConfigurationRequestBody holds request body parameters for UpdateConfiguration + UpdateConfigurationRequestBody struct { CapacityAlertsThreshold *int `json:"capacityAlertsThreshold,omitempty"` Comments string `json:"comments"` Locations []ConfigLocationReq `json:"locations"` @@ -236,8 +208,8 @@ func (r CreateConfigurationRequest) Validate() error { }) } -// Validate validates CreateConfigurationBody -func (b CreateConfigurationBody) Validate() error { +// Validate validates CreateConfigurationRequestBody +func (b CreateConfigurationRequestBody) Validate() error { return validation.Errors{ "Comments": validation.Validate(b.Comments, validation.Required), "Locations": validation.Validate(b.Locations, validation.Required), @@ -257,8 +229,8 @@ func (r UpdateConfigurationRequest) Validate() error { }) } -// Validate validates UpdateConfigurationBody -func (b UpdateConfigurationBody) Validate() error { +// Validate validates UpdateConfigurationRequestBody +func (b UpdateConfigurationRequestBody) Validate() error { return validation.Errors{ "Comments": validation.Validate(b.Comments, validation.Required), "Locations": validation.Validate(b.Locations, validation.Required), diff --git a/pkg/cloudwrapper/configurations_test.go b/pkg/cloudwrapper/configurations_test.go index 9f5cc7ed..4e0656e1 100644 --- a/pkg/cloudwrapper/configurations_test.go +++ b/pkg/cloudwrapper/configurations_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -130,7 +130,7 @@ func TestGetConfiguration(t *testing.T) { }`, expectedPath: "/cloud-wrapper/v1/configurations/1", expectedResponse: &Configuration{ - CapacityAlertsThreshold: tools.IntPtr(75), + CapacityAlertsThreshold: ptr.To(75), Comments: "TestComments", ContractID: "TestContractID", ConfigID: 1, @@ -195,7 +195,7 @@ func TestGetConfiguration(t *testing.T) { DataStreams: &DataStreams{ DataStreamIDs: []int64{11, 22}, Enabled: true, - SamplingRate: tools.IntPtr(999), + SamplingRate: ptr.To(999), }, EnableSoftAlerts: true, Origins: []Origin{ @@ -215,8 +215,8 @@ func TestGetConfiguration(t *testing.T) { ConfigName: "TestConfigName", LastUpdatedBy: "user", LastUpdatedDate: "2023-05-10T09:55:37.000Z", - LastActivatedBy: tools.StringPtr("user"), - LastActivatedDate: tools.StringPtr("2023-05-10T10:14:49.379Z"), + LastActivatedBy: ptr.To("user"), + LastActivatedDate: ptr.To("2023-05-10T10:14:49.379Z"), NotificationEmails: []string{"test@akamai.com"}, PropertyIDs: []string{ "321", @@ -455,7 +455,7 @@ func TestListConfigurations(t *testing.T) { expectedResponse: &ListConfigurationsResponse{ Configurations: []Configuration{ { - CapacityAlertsThreshold: tools.IntPtr(75), + CapacityAlertsThreshold: ptr.To(75), Comments: "testComments", ContractID: "testContract", ConfigID: 1, @@ -474,8 +474,8 @@ func TestListConfigurations(t *testing.T) { ConfigName: "testcloudwrapper", LastUpdatedBy: "user", LastUpdatedDate: "2023-05-10T09:55:37.000Z", - LastActivatedBy: tools.StringPtr("user"), - LastActivatedDate: tools.StringPtr("2023-05-10T10:14:49.379Z"), + LastActivatedBy: ptr.To("user"), + LastActivatedDate: ptr.To("2023-05-10T10:14:49.379Z"), NotificationEmails: []string{"user@akamai.com"}, PropertyIDs: []string{ "11", @@ -483,7 +483,7 @@ func TestListConfigurations(t *testing.T) { RetainIdleObjects: false, }, { - CapacityAlertsThreshold: tools.IntPtr(75), + CapacityAlertsThreshold: ptr.To(75), Comments: "mcdn", ContractID: "testContract2", ConfigID: 2, @@ -534,8 +534,8 @@ func TestListConfigurations(t *testing.T) { ConfigName: "testcloudwrappermcdn", LastUpdatedBy: "user", LastUpdatedDate: "2023-05-10T09:55:37.000Z", - LastActivatedBy: tools.StringPtr("user"), - LastActivatedDate: tools.StringPtr("2023-05-10T10:14:49.379Z"), + LastActivatedBy: ptr.To("user"), + LastActivatedDate: ptr.To("2023-05-10T10:14:49.379Z"), NotificationEmails: []string{"user@akamai.com"}, PropertyIDs: []string{"22"}, RetainIdleObjects: false, @@ -597,7 +597,7 @@ func TestCreateConfiguration(t *testing.T) { }{ "200 OK - minimal": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -668,7 +668,7 @@ func TestCreateConfiguration(t *testing.T) { "lastActivatedBy":null }`, expectedResponse: &Configuration{ - CapacityAlertsThreshold: tools.IntPtr(50), + CapacityAlertsThreshold: ptr.To(50), Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationResp{ @@ -694,7 +694,7 @@ func TestCreateConfiguration(t *testing.T) { "200 OK - minimal with activate query param": { params: CreateConfigurationRequest{ Activate: true, - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -765,7 +765,7 @@ func TestCreateConfiguration(t *testing.T) { "lastActivatedBy":null }`, expectedResponse: &Configuration{ - CapacityAlertsThreshold: tools.IntPtr(50), + CapacityAlertsThreshold: ptr.To(50), Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationResp{ @@ -790,7 +790,7 @@ func TestCreateConfiguration(t *testing.T) { }, "200 OK - minimal MultiCDNSettings": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1005,8 +1005,8 @@ func TestCreateConfiguration(t *testing.T) { }, "200 OK - full MultiCDNSettings": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ - CapacityAlertsThreshold: tools.IntPtr(70), + Body: CreateConfigurationRequestBody{ + CapacityAlertsThreshold: ptr.To(70), Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1061,7 +1061,7 @@ func TestCreateConfiguration(t *testing.T) { DataStreams: &DataStreams{ DataStreamIDs: []int64{1}, Enabled: true, - SamplingRate: tools.IntPtr(10), + SamplingRate: ptr.To(10), }, Origins: []Origin{ { @@ -1266,7 +1266,7 @@ func TestCreateConfiguration(t *testing.T) { "lastActivatedBy":null }`, expectedResponse: &Configuration{ - CapacityAlertsThreshold: tools.IntPtr(70), + CapacityAlertsThreshold: ptr.To(70), Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationResp{ @@ -1322,7 +1322,7 @@ func TestCreateConfiguration(t *testing.T) { DataStreams: &DataStreams{ DataStreamIDs: []int64{1}, Enabled: true, - SamplingRate: tools.IntPtr(10), + SamplingRate: ptr.To(10), }, Origins: []Origin{ { @@ -1350,7 +1350,7 @@ func TestCreateConfiguration(t *testing.T) { }, "200 OK - BOCC struct fields default values": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1547,7 +1547,7 @@ func TestCreateConfiguration(t *testing.T) { }, "200 OK - DataStreams struct fields default values": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1749,7 +1749,7 @@ func TestCreateConfiguration(t *testing.T) { }, "missing required params - location fields": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1769,7 +1769,7 @@ func TestCreateConfiguration(t *testing.T) { }, "missing required params - multiCDN fields": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1793,7 +1793,7 @@ func TestCreateConfiguration(t *testing.T) { }, "missing required params - BOCC struct fields when enabled": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1840,7 +1840,7 @@ func TestCreateConfiguration(t *testing.T) { }, "missing required params - Origin struct fields": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{Comments: "TestComments", + Body: CreateConfigurationRequestBody{Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ { @@ -1880,7 +1880,7 @@ func TestCreateConfiguration(t *testing.T) { }, "validation error - at least one CDN must be enabled": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1930,7 +1930,7 @@ func TestCreateConfiguration(t *testing.T) { }, "validation error - authKeys nor IPACLCIDRs specified": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -1974,8 +1974,8 @@ func TestCreateConfiguration(t *testing.T) { }, "struct fields validations": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ - CapacityAlertsThreshold: tools.IntPtr(20), + Body: CreateConfigurationRequestBody{ + CapacityAlertsThreshold: ptr.To(20), Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -2013,7 +2013,7 @@ func TestCreateConfiguration(t *testing.T) { DataStreams: &DataStreams{ DataStreamIDs: []int64{1}, Enabled: true, - SamplingRate: tools.IntPtr(-10), + SamplingRate: ptr.To(-10), }, Origins: []Origin{ { @@ -2033,7 +2033,7 @@ func TestCreateConfiguration(t *testing.T) { }, "500 internal server error": { params: CreateConfigurationRequest{ - Body: CreateConfigurationBody{ + Body: CreateConfigurationRequestBody{ Comments: "TestComments", ContractID: "TestContractID", Locations: []ConfigLocationReq{ @@ -2074,9 +2074,6 @@ func TestCreateConfiguration(t *testing.T) { } for name, test := range tests { - if name != "200 OK - full MultiCDNSettings" { - continue - } t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, test.expectedPath, r.URL.String()) @@ -2115,7 +2112,7 @@ func TestUpdateConfiguration(t *testing.T) { "200 OK - minimal": { params: UpdateConfigurationRequest{ ConfigID: 111, - Body: UpdateConfigurationBody{ + Body: UpdateConfigurationRequestBody{ Comments: "TestCommentsUpdated", Locations: []ConfigLocationReq{ { @@ -2185,7 +2182,7 @@ func TestUpdateConfiguration(t *testing.T) { }`, expectedResponse: &Configuration{ ConfigID: 111, - CapacityAlertsThreshold: tools.IntPtr(50), + CapacityAlertsThreshold: ptr.To(50), Comments: "TestCommentsUpdated", ContractID: "TestContractID", Locations: []ConfigLocationResp{ @@ -2211,7 +2208,7 @@ func TestUpdateConfiguration(t *testing.T) { "200 OK - minimal MultiCDNSettings": { params: UpdateConfigurationRequest{ ConfigID: 111, - Body: UpdateConfigurationBody{ + Body: UpdateConfigurationRequestBody{ Comments: "TestCommentsUpdated", Locations: []ConfigLocationReq{ { @@ -2405,8 +2402,8 @@ func TestUpdateConfiguration(t *testing.T) { "200 OK - all fields": { params: UpdateConfigurationRequest{ ConfigID: 111, - Body: UpdateConfigurationBody{ - CapacityAlertsThreshold: tools.IntPtr(80), + Body: UpdateConfigurationRequestBody{ + CapacityAlertsThreshold: ptr.To(80), Comments: "TestCommentsUpdated", Locations: []ConfigLocationReq{ { @@ -2447,7 +2444,7 @@ func TestUpdateConfiguration(t *testing.T) { DataStreams: &DataStreams{ DataStreamIDs: []int64{1}, Enabled: true, - SamplingRate: tools.IntPtr(10), + SamplingRate: ptr.To(10), }, Origins: []Origin{ { @@ -2606,7 +2603,7 @@ func TestUpdateConfiguration(t *testing.T) { "lastActivatedBy":null }`, expectedResponse: &Configuration{ - CapacityAlertsThreshold: tools.IntPtr(80), + CapacityAlertsThreshold: ptr.To(80), Comments: "TestCommentsUpdated", ContractID: "TestContractID", ConfigID: 111, @@ -2650,7 +2647,7 @@ func TestUpdateConfiguration(t *testing.T) { DataStreams: &DataStreams{ DataStreamIDs: []int64{1}, Enabled: true, - SamplingRate: tools.IntPtr(10), + SamplingRate: ptr.To(10), }, EnableSoftAlerts: true, Origins: []Origin{ @@ -2681,7 +2678,7 @@ func TestUpdateConfiguration(t *testing.T) { "500 internal server error": { params: UpdateConfigurationRequest{ ConfigID: 1, - Body: UpdateConfigurationBody{ + Body: UpdateConfigurationRequestBody{ Comments: "TestCommentsUpdated", Locations: []ConfigLocationReq{ { diff --git a/pkg/cloudwrapper/errors.go b/pkg/cloudwrapper/errors.go index 428b2b9b..b0ddc7b2 100644 --- a/pkg/cloudwrapper/errors.go +++ b/pkg/cloudwrapper/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/cloudwrapper/errors_test.go b/pkg/cloudwrapper/errors_test.go index aa030f54..4337ed00 100644 --- a/pkg/cloudwrapper/errors_test.go +++ b/pkg/cloudwrapper/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/cloudwrapper/locations.go b/pkg/cloudwrapper/locations.go index 1ed99ab2..53f6f986 100644 --- a/pkg/cloudwrapper/locations.go +++ b/pkg/cloudwrapper/locations.go @@ -8,14 +8,6 @@ import ( ) type ( - // Locations is the cloudwrapper location API interface - Locations interface { - // ListLocations returns a list of locations available to distribute Cloud Wrapper capacity - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-locations - ListLocations(context.Context) (*ListLocationResponse, error) - } - // ListLocationResponse represents a response object returned by ListLocations ListLocationResponse struct { Locations []Location `json:"locations"` diff --git a/pkg/cloudwrapper/multi_cdn.go b/pkg/cloudwrapper/multi_cdn.go index 8824ea2e..6b61b034 100644 --- a/pkg/cloudwrapper/multi_cdn.go +++ b/pkg/cloudwrapper/multi_cdn.go @@ -7,24 +7,11 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // MultiCDN is the cloudwrapper Multi-CDN API interface - MultiCDN interface { - // ListAuthKeys lists the cdnAuthKeys for a specified contractId and cdnCode - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-auth-keys - ListAuthKeys(context.Context, ListAuthKeysRequest) (*ListAuthKeysResponse, error) - - // ListCDNProviders lists CDN providers - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-providers - ListCDNProviders(context.Context) (*ListCDNProvidersResponse, error) - } - // ListAuthKeysRequest is a request struct ListAuthKeysRequest struct { ContractID string diff --git a/pkg/cloudwrapper/properties.go b/pkg/cloudwrapper/properties.go index e4b12f89..9dec3dcb 100644 --- a/pkg/cloudwrapper/properties.go +++ b/pkg/cloudwrapper/properties.go @@ -8,23 +8,11 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Properties is a CloudWrapper properties API interface - Properties interface { - // ListProperties lists unused properties - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-properties - ListProperties(context.Context, ListPropertiesRequest) (*ListPropertiesResponse, error) - // ListOrigins lists property origins - // - // See: https://techdocs.akamai.com/cloud-wrapper/reference/get-origins - ListOrigins(context.Context, ListOriginsRequest) (*ListOriginsResponse, error) - } - // ListPropertiesRequest holds parameters for ListProperties ListPropertiesRequest struct { Unused bool diff --git a/pkg/cps/change_management_info.go b/pkg/cps/change_management_info.go index 4e3c222f..ad44f800 100644 --- a/pkg/cps/change_management_info.go +++ b/pkg/cps/change_management_info.go @@ -8,25 +8,6 @@ import ( ) type ( - // ChangeManagementInfo is a CPS API enabling change management - ChangeManagementInfo interface { - // GetChangeManagementInfo gets information about acknowledgement status, - // and may include warnings about potential conflicts that may occur if you proceed with acknowledgement - // - // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param - GetChangeManagementInfo(ctx context.Context, params GetChangeRequest) (*ChangeManagementInfoResponse, error) - - // GetChangeDeploymentInfo gets deployment currently deployed to the staging network - // - // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param - GetChangeDeploymentInfo(ctx context.Context, params GetChangeRequest) (*ChangeDeploymentInfoResponse, error) - - // AcknowledgeChangeManagement sends acknowledgement request to CPS to proceed deploying the certificate to the production network - // - // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param - AcknowledgeChangeManagement(context.Context, AcknowledgementRequest) error - } - // ChangeManagementInfoResponse contains response from GetChangeManagementInfo ChangeManagementInfoResponse struct { AcknowledgementDeadline *string `json:"acknowledgementDeadline"` diff --git a/pkg/cps/change_management_info_test.go b/pkg/cps/change_management_info_test.go index 9e1bc348..4fc10203 100644 --- a/pkg/cps/change_management_info_test.go +++ b/pkg/cps/change_management_info_test.go @@ -7,8 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -225,7 +224,7 @@ func TestGetChangeDeploymentInfo(t *testing.T) { }, MultiStackedCertificates: []DeploymentCertificate{}, OCSPURIs: []string{}, - OCSPStapled: tools.BoolPtr(false), + OCSPStapled: ptr.To(false), }, }, "500 internal server error": { diff --git a/pkg/cps/changes.go b/pkg/cps/changes.go index 785ebb70..161c3b4b 100644 --- a/pkg/cps/changes.go +++ b/pkg/cps/changes.go @@ -11,28 +11,6 @@ import ( ) type ( - // ChangeOperations is a CPS change API interface - ChangeOperations interface { - // GetChangeStatus fetches change status for given enrollment and change ID - // - // See: https://techdocs.akamai.com/cps/reference/get-enrollment-change - GetChangeStatus(context.Context, GetChangeStatusRequest) (*Change, error) - - // CancelChange cancels a pending change - // - // See: https://techdocs.akamai.com/cps/reference/delete-enrollment-change - CancelChange(context.Context, CancelChangeRequest) (*CancelChangeResponse, error) - - // UpdateChange updates a pending change - // Deprecated: this function will be removed in a future release. Use one of: - // AcknowledgeChangeManagement(), AcknowledgePostVerificationWarnings(), - // AcknowledgePreVerificationWarnings(), UploadThirdPartyCertAndTrustChain() - // or AcknowledgeDVChallenges() - // - // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param - UpdateChange(context.Context, UpdateChangeRequest) (*UpdateChangeResponse, error) - } - // Change contains change status information Change struct { AllowedInput []AllowedInput `json:"allowedInput"` diff --git a/pkg/cps/cps.go b/pkg/cps/cps.go index c8166158..55269e08 100644 --- a/pkg/cps/cps.go +++ b/pkg/cps/cps.go @@ -2,9 +2,10 @@ package cps import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,16 +16,164 @@ var ( type ( // CPS is the cps api interface CPS interface { - ChangeManagementInfo - ChangeOperations - Deployments - DeploymentSchedules - DVChallenges - Enrollments - History - PostVerification - PreVerification - ThirdPartyCSR + // ChangeManagementInfo + + // GetChangeManagementInfo gets information about acknowledgement status, + // and may include warnings about potential conflicts that may occur if you proceed with acknowledgement + // + // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param + GetChangeManagementInfo(ctx context.Context, params GetChangeRequest) (*ChangeManagementInfoResponse, error) + + // GetChangeDeploymentInfo gets deployment currently deployed to the staging network + // + // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param + GetChangeDeploymentInfo(ctx context.Context, params GetChangeRequest) (*ChangeDeploymentInfoResponse, error) + + // AcknowledgeChangeManagement sends acknowledgement request to CPS to proceed deploying the certificate to the production network + // + // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param + AcknowledgeChangeManagement(context.Context, AcknowledgementRequest) error + + // GetChangeStatus fetches change status for given enrollment and change ID + // + // See: https://techdocs.akamai.com/cps/reference/get-enrollment-change + GetChangeStatus(context.Context, GetChangeStatusRequest) (*Change, error) + + // ChangeOperations + + // CancelChange cancels a pending change + // + // See: https://techdocs.akamai.com/cps/reference/delete-enrollment-change + CancelChange(context.Context, CancelChangeRequest) (*CancelChangeResponse, error) + + // UpdateChange updates a pending change + // Deprecated: this function will be removed in a future release. Use one of: + // AcknowledgeChangeManagement(), AcknowledgePostVerificationWarnings(), + // AcknowledgePreVerificationWarnings(), UploadThirdPartyCertAndTrustChain() + // or AcknowledgeDVChallenges() + // + // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param + UpdateChange(context.Context, UpdateChangeRequest) (*UpdateChangeResponse, error) + + // Deployments + + // ListDeployments fetches deployments for given enrollment + // + // See: https://techdocs.akamai.com/cps/reference/get-deployments + ListDeployments(context.Context, ListDeploymentsRequest) (*ListDeploymentsResponse, error) + + // GetProductionDeployment fetches production deployment for given enrollment + // + // See: https://techdocs.akamai.com/cps/reference/get-deployments-production + GetProductionDeployment(context.Context, GetDeploymentRequest) (*GetProductionDeploymentResponse, error) + + // GetStagingDeployment fetches staging deployment for given enrollment + // + // See: https://techdocs.akamai.com/cps/reference/get-deployment-staging + GetStagingDeployment(context.Context, GetDeploymentRequest) (*GetStagingDeploymentResponse, error) + + // DeploymentSchedules + + // GetDeploymentSchedule fetches the current deployment schedule settings describing when a change deploys to the network + // + // See: https://techdocs.akamai.com/cps/reference/get-change-deployment-schedule + GetDeploymentSchedule(context.Context, GetDeploymentScheduleRequest) (*DeploymentSchedule, error) + + // UpdateDeploymentSchedule updates the current deployment schedule + // + // See: https://techdocs.akamai.com/cps/reference/put-change-deployment-schedule + UpdateDeploymentSchedule(context.Context, UpdateDeploymentScheduleRequest) (*UpdateDeploymentScheduleResponse, error) + + // DVChallenges + + // GetChangeLetsEncryptChallenges gets detailed information about Domain Validation challenges + // + // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param + GetChangeLetsEncryptChallenges(context.Context, GetChangeRequest) (*DVArray, error) + + // AcknowledgeDVChallenges sends acknowledgement request to CPS informing that the validation is completed + // + // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param + AcknowledgeDVChallenges(context.Context, AcknowledgementRequest) error + + // Enrollments + + // ListEnrollments fetches all enrollments with given contractId + // + // See https://techdocs.akamai.com/cps/reference/get-enrollments + ListEnrollments(context.Context, ListEnrollmentsRequest) (*ListEnrollmentsResponse, error) + + // GetEnrollment fetches enrollment object with given ID + // + // See: https://techdocs.akamai.com/cps/reference/get-enrollment + GetEnrollment(context.Context, GetEnrollmentRequest) (*GetEnrollmentResponse, error) + + // CreateEnrollment creates a new enrollment + // + // See: https://techdocs.akamai.com/cps/reference/post-enrollment + CreateEnrollment(context.Context, CreateEnrollmentRequest) (*CreateEnrollmentResponse, error) + + // UpdateEnrollment updates a single enrollment entry with given ID + // + // See: https://techdocs.akamai.com/cps/reference/put-enrollment + UpdateEnrollment(context.Context, UpdateEnrollmentRequest) (*UpdateEnrollmentResponse, error) + + // RemoveEnrollment removes an enrollment with given ID + // + // See: https://techdocs.akamai.com/cps/reference/delete-enrollment + RemoveEnrollment(context.Context, RemoveEnrollmentRequest) (*RemoveEnrollmentResponse, error) + + // History + + // GetDVHistory is a domain name validation history for the enrollment + // + // See: https://techdocs.akamai.com/cps/reference/get-dv-history + GetDVHistory(context.Context, GetDVHistoryRequest) (*GetDVHistoryResponse, error) + + // GetCertificateHistory views the certificate history. + // + // See: https://techdocs.akamai.com/cps/reference/get-history-certificates + GetCertificateHistory(context.Context, GetCertificateHistoryRequest) (*GetCertificateHistoryResponse, error) + + // GetChangeHistory views the change history for enrollment. + // + // See: https://techdocs.akamai.com/cps/reference/get-history-changes + GetChangeHistory(context.Context, GetChangeHistoryRequest) (*GetChangeHistoryResponse, error) + + // PostVerification + + // GetChangePostVerificationWarnings gets information about post verification warnings + // + // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param + GetChangePostVerificationWarnings(ctx context.Context, params GetChangeRequest) (*PostVerificationWarnings, error) + // AcknowledgePostVerificationWarnings sends acknowledgement request to CPS informing that the warnings should be ignored + // + // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param + AcknowledgePostVerificationWarnings(context.Context, AcknowledgementRequest) error + + // PreVerification + + // GetChangePreVerificationWarnings gets detailed information about Domain Validation challenges + // + // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param + GetChangePreVerificationWarnings(ctx context.Context, params GetChangeRequest) (*PreVerificationWarnings, error) + + // AcknowledgePreVerificationWarnings sends acknowledgement request to CPS informing that the warnings should be ignored + // + // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param + AcknowledgePreVerificationWarnings(context.Context, AcknowledgementRequest) error + + // ThirdPartyCSR + + // GetChangeThirdPartyCSR gets certificate signing request + // + // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param + GetChangeThirdPartyCSR(ctx context.Context, params GetChangeRequest) (*ThirdPartyCSRResponse, error) + + // UploadThirdPartyCertAndTrustChain uploads signed certificate and trust chain to cps + // + // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param + UploadThirdPartyCertAndTrustChain(context.Context, UploadThirdPartyCertAndTrustChainRequest) error } cps struct { diff --git a/pkg/cps/cps_test.go b/pkg/cps/cps_test.go index 40aea834..4625e2cf 100644 --- a/pkg/cps/cps_test.go +++ b/pkg/cps/cps_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cps/deployment_schedules.go b/pkg/cps/deployment_schedules.go index 54bdd75a..1c622e69 100644 --- a/pkg/cps/deployment_schedules.go +++ b/pkg/cps/deployment_schedules.go @@ -10,19 +10,6 @@ import ( ) type ( - // DeploymentSchedules is a CPS deployment schedules API interface - DeploymentSchedules interface { - // GetDeploymentSchedule fetches the current deployment schedule settings describing when a change deploys to the network - // - // See: https://techdocs.akamai.com/cps/reference/get-change-deployment-schedule - GetDeploymentSchedule(context.Context, GetDeploymentScheduleRequest) (*DeploymentSchedule, error) - - // UpdateDeploymentSchedule updates the current deployment schedule - // - // See: https://techdocs.akamai.com/cps/reference/put-change-deployment-schedule - UpdateDeploymentSchedule(context.Context, UpdateDeploymentScheduleRequest) (*UpdateDeploymentScheduleResponse, error) - } - // GetDeploymentScheduleRequest contains parameters for GetDeploymentSchedule GetDeploymentScheduleRequest struct { ChangeID int diff --git a/pkg/cps/deployment_schedules_test.go b/pkg/cps/deployment_schedules_test.go index 4f145882..13692bca 100644 --- a/pkg/cps/deployment_schedules_test.go +++ b/pkg/cps/deployment_schedules_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,8 +38,8 @@ func TestGetDeploymentSchedule(t *testing.T) { "Accept": "application/vnd.akamai.cps.deployment-schedule.v1+json", }, expectedResponse: &DeploymentSchedule{ - NotAfter: tools.StringPtr("2021-11-03T08:02:46.655484Z"), - NotBefore: tools.StringPtr("2021-10-03T08:02:46.655484Z"), + NotAfter: ptr.To("2021-11-03T08:02:46.655484Z"), + NotBefore: ptr.To("2021-10-03T08:02:46.655484Z"), }, }, "500 internal server error": { @@ -128,8 +128,8 @@ func TestUpdateDeploymentSchedule(t *testing.T) { ChangeID: 1, EnrollmentID: 10, DeploymentSchedule: DeploymentSchedule{ - NotAfter: tools.StringPtr("2021-11-03T08:02:46.655484Z"), - NotBefore: tools.StringPtr("2021-10-03T08:02:46.655484Z"), + NotAfter: ptr.To("2021-11-03T08:02:46.655484Z"), + NotBefore: ptr.To("2021-10-03T08:02:46.655484Z"), }, }, responseStatus: http.StatusOK, @@ -151,8 +151,8 @@ func TestUpdateDeploymentSchedule(t *testing.T) { ChangeID: 1, EnrollmentID: 10, DeploymentSchedule: DeploymentSchedule{ - NotAfter: tools.StringPtr("2021-11-03T08:02:46.655484Z"), - NotBefore: tools.StringPtr("2021-10-03T08:02:46.655484Z"), + NotAfter: ptr.To("2021-11-03T08:02:46.655484Z"), + NotBefore: ptr.To("2021-10-03T08:02:46.655484Z"), }, }, responseStatus: http.StatusInternalServerError, @@ -178,8 +178,8 @@ func TestUpdateDeploymentSchedule(t *testing.T) { params: UpdateDeploymentScheduleRequest{ EnrollmentID: 10, DeploymentSchedule: DeploymentSchedule{ - NotAfter: tools.StringPtr("2021-11-03T08:02:46.655484Z"), - NotBefore: tools.StringPtr("2021-10-03T08:02:46.655484Z"), + NotAfter: ptr.To("2021-11-03T08:02:46.655484Z"), + NotBefore: ptr.To("2021-10-03T08:02:46.655484Z"), }, }, expectedPath: "/cps/v2/enrollments/10/changes/1/deployment-schedule", @@ -192,8 +192,8 @@ func TestUpdateDeploymentSchedule(t *testing.T) { params: UpdateDeploymentScheduleRequest{ ChangeID: 1, DeploymentSchedule: DeploymentSchedule{ - NotAfter: tools.StringPtr("2021-11-03T08:02:46.655484Z"), - NotBefore: tools.StringPtr("2021-10-03T08:02:46.655484Z"), + NotAfter: ptr.To("2021-11-03T08:02:46.655484Z"), + NotBefore: ptr.To("2021-10-03T08:02:46.655484Z"), }, }, expectedPath: "/cps/v2/enrollments/10/changes/1/deployment-schedule", diff --git a/pkg/cps/deployments.go b/pkg/cps/deployments.go index b89f9ec1..b46551a5 100644 --- a/pkg/cps/deployments.go +++ b/pkg/cps/deployments.go @@ -10,24 +10,6 @@ import ( ) type ( - // Deployments is a CPS deployments API interface - Deployments interface { - // ListDeployments fetches deployments for given enrollment - // - // See: https://techdocs.akamai.com/cps/reference/get-deployments - ListDeployments(context.Context, ListDeploymentsRequest) (*ListDeploymentsResponse, error) - - // GetProductionDeployment fetches production deployment for given enrollment - // - // See: https://techdocs.akamai.com/cps/reference/get-deployments-production - GetProductionDeployment(context.Context, GetDeploymentRequest) (*GetProductionDeploymentResponse, error) - - // GetStagingDeployment fetches staging deployment for given enrollment - // - // See: https://techdocs.akamai.com/cps/reference/get-deployment-staging - GetStagingDeployment(context.Context, GetDeploymentRequest) (*GetStagingDeploymentResponse, error) - } - // ListDeploymentsRequest contains parameters for ListDeployments ListDeploymentsRequest struct { EnrollmentID int diff --git a/pkg/cps/deployments_test.go b/pkg/cps/deployments_test.go index 385a6989..0718c35e 100644 --- a/pkg/cps/deployments_test.go +++ b/pkg/cps/deployments_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -103,7 +103,7 @@ func TestListDeployments(t *testing.T) { }, expectedResponse: &ListDeploymentsResponse{ Production: &Deployment{ - OCSPStapled: tools.BoolPtr(false), + OCSPStapled: ptr.To(false), OCSPURIs: []string{}, NetworkConfiguration: DeploymentNetworkConfiguration{ Geography: "core", @@ -138,7 +138,7 @@ func TestListDeployments(t *testing.T) { }, }, Staging: &Deployment{ - OCSPStapled: tools.BoolPtr(false), + OCSPStapled: ptr.To(false), OCSPURIs: []string{}, NetworkConfiguration: DeploymentNetworkConfiguration{ Geography: "core", @@ -277,7 +277,7 @@ func TestGetProductionDeployment(t *testing.T) { "Accept": "application/vnd.akamai.cps.deployment.v8+json", }, expectedResponse: &GetProductionDeploymentResponse{ - OCSPStapled: tools.BoolPtr(false), + OCSPStapled: ptr.To(false), OCSPURIs: []string{}, NetworkConfiguration: DeploymentNetworkConfiguration{ Geography: "core", @@ -422,7 +422,7 @@ func TestGetStagingDeployment(t *testing.T) { }, expectedResponse: &GetStagingDeploymentResponse{ - OCSPStapled: tools.BoolPtr(false), + OCSPStapled: ptr.To(false), OCSPURIs: []string{}, NetworkConfiguration: DeploymentNetworkConfiguration{ Geography: "core", diff --git a/pkg/cps/dv_challenges.go b/pkg/cps/dv_challenges.go index 3767dd85..91356f0d 100644 --- a/pkg/cps/dv_challenges.go +++ b/pkg/cps/dv_challenges.go @@ -9,19 +9,6 @@ import ( ) type ( - // DVChallenges is a CPS DV challenges API interface - DVChallenges interface { - // GetChangeLetsEncryptChallenges gets detailed information about Domain Validation challenges - // - // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param - GetChangeLetsEncryptChallenges(context.Context, GetChangeRequest) (*DVArray, error) - - // AcknowledgeDVChallenges sends acknowledgement request to CPS informing that the validation is completed - // - // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param - AcknowledgeDVChallenges(context.Context, AcknowledgementRequest) error - } - // DVArray is an array of DV objects DVArray struct { DV []DV `json:"dv"` diff --git a/pkg/cps/enrollments.go b/pkg/cps/enrollments.go index ae83b04a..a471ff05 100644 --- a/pkg/cps/enrollments.go +++ b/pkg/cps/enrollments.go @@ -12,34 +12,6 @@ import ( ) type ( - // Enrollments is a CPS enrollments API interface - Enrollments interface { - // ListEnrollments fetches all enrollments with given contractId - // - // See https://techdocs.akamai.com/cps/reference/get-enrollments - ListEnrollments(context.Context, ListEnrollmentsRequest) (*ListEnrollmentsResponse, error) - - // GetEnrollment fetches enrollment object with given ID - // - // See: https://techdocs.akamai.com/cps/reference/get-enrollment - GetEnrollment(context.Context, GetEnrollmentRequest) (*GetEnrollmentResponse, error) - - // CreateEnrollment creates a new enrollment - // - // See: https://techdocs.akamai.com/cps/reference/post-enrollment - CreateEnrollment(context.Context, CreateEnrollmentRequest) (*CreateEnrollmentResponse, error) - - // UpdateEnrollment updates a single enrollment entry with given ID - // - // See: https://techdocs.akamai.com/cps/reference/put-enrollment - UpdateEnrollment(context.Context, UpdateEnrollmentRequest) (*UpdateEnrollmentResponse, error) - - // RemoveEnrollment removes an enrollment with given ID - // - // See: https://techdocs.akamai.com/cps/reference/delete-enrollment - RemoveEnrollment(context.Context, RemoveEnrollmentRequest) (*RemoveEnrollmentResponse, error) - } - // ListEnrollmentsResponse represents list of CPS enrollment objects under given contractId. It is used as a response body while fetching enrollments by contractId ListEnrollmentsResponse struct { Enrollments []Enrollment `json:"enrollments"` diff --git a/pkg/cps/enrollments_test.go b/pkg/cps/enrollments_test.go index 451f02fc..bc864f1e 100644 --- a/pkg/cps/enrollments_test.go +++ b/pkg/cps/enrollments_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1110,7 +1110,7 @@ func TestCreateEnrollment(t *testing.T) { }, NetworkConfiguration: &NetworkConfiguration{}, Org: &Org{Name: "Akamai"}, - OrgID: tools.IntPtr(10), + OrgID: ptr.To(10), RA: "third-party", TechContact: &Contact{ Email: "r2d2@akamai.com", @@ -1374,7 +1374,7 @@ func TestUpdateEnrollment(t *testing.T) { }, NetworkConfiguration: &NetworkConfiguration{}, Org: &Org{Name: "Akamai"}, - OrgID: tools.IntPtr(20), + OrgID: ptr.To(20), RA: "third-party", TechContact: &Contact{ Email: "r2d2@akamai.com", diff --git a/pkg/cps/errors_test.go b/pkg/cps/errors_test.go index 416df6db..9fd85af5 100644 --- a/pkg/cps/errors_test.go +++ b/pkg/cps/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/cps/history.go b/pkg/cps/history.go index 51366f1a..957f88fe 100644 --- a/pkg/cps/history.go +++ b/pkg/cps/history.go @@ -10,24 +10,6 @@ import ( ) type ( - // History is a CPS interface for History management - History interface { - // GetDVHistory is a domain name validation history for the enrollment - // - // See: https://techdocs.akamai.com/cps/reference/get-dv-history - GetDVHistory(context.Context, GetDVHistoryRequest) (*GetDVHistoryResponse, error) - - // GetCertificateHistory views the certificate history. - // - // See: https://techdocs.akamai.com/cps/reference/get-history-certificates - GetCertificateHistory(context.Context, GetCertificateHistoryRequest) (*GetCertificateHistoryResponse, error) - - // GetChangeHistory views the change history for enrollment. - // - // See: https://techdocs.akamai.com/cps/reference/get-history-changes - GetChangeHistory(context.Context, GetChangeHistoryRequest) (*GetChangeHistoryResponse, error) - } - // GetDVHistoryRequest represents request for GetDVHistory operation GetDVHistoryRequest struct { EnrollmentID int diff --git a/pkg/cps/post_verification_warnings.go b/pkg/cps/post_verification_warnings.go index 6fa39cfc..bad0a129 100644 --- a/pkg/cps/post_verification_warnings.go +++ b/pkg/cps/post_verification_warnings.go @@ -8,18 +8,6 @@ import ( ) type ( - // PostVerification is a CPS API enabling management of post-verification-warnings - PostVerification interface { - // GetChangePostVerificationWarnings gets information about post verification warnings - // - // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param - GetChangePostVerificationWarnings(ctx context.Context, params GetChangeRequest) (*PostVerificationWarnings, error) - // AcknowledgePostVerificationWarnings sends acknowledgement request to CPS informing that the warnings should be ignored - // - // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param - AcknowledgePostVerificationWarnings(context.Context, AcknowledgementRequest) error - } - // PostVerificationWarnings is a response object containing all warnings encountered during enrollment post-verification PostVerificationWarnings struct { Warnings string `json:"warnings"` diff --git a/pkg/cps/pre_verification_warnings.go b/pkg/cps/pre_verification_warnings.go index 1f236ad9..b0960a2d 100644 --- a/pkg/cps/pre_verification_warnings.go +++ b/pkg/cps/pre_verification_warnings.go @@ -9,19 +9,6 @@ import ( ) type ( - // PreVerification is a CPS API enabling management of pre-verification-warnings - PreVerification interface { - // GetChangePreVerificationWarnings gets detailed information about Domain Validation challenges - // - // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param - GetChangePreVerificationWarnings(ctx context.Context, params GetChangeRequest) (*PreVerificationWarnings, error) - - // AcknowledgePreVerificationWarnings sends acknowledgement request to CPS informing that the warnings should be ignored - // - // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param - AcknowledgePreVerificationWarnings(context.Context, AcknowledgementRequest) error - } - // PreVerificationWarnings is a response object containing all warnings encountered during enrollment pre-verification PreVerificationWarnings struct { Warnings string `json:"warnings"` diff --git a/pkg/cps/third_party_csr.go b/pkg/cps/third_party_csr.go index fba00799..8a18185c 100644 --- a/pkg/cps/third_party_csr.go +++ b/pkg/cps/third_party_csr.go @@ -6,24 +6,11 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // ThirdPartyCSR is a CPS API enabling management of third-party certificates - ThirdPartyCSR interface { - // GetChangeThirdPartyCSR gets certificate signing request - // - // See: https://techdocs.akamai.com/cps/reference/get-change-allowed-input-param - GetChangeThirdPartyCSR(ctx context.Context, params GetChangeRequest) (*ThirdPartyCSRResponse, error) - - // UploadThirdPartyCertAndTrustChain uploads signed certificate and trust chain to cps - // - // See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param - UploadThirdPartyCertAndTrustChain(context.Context, UploadThirdPartyCertAndTrustChainRequest) error - } - // ThirdPartyCSRResponse is a response object containing list of csrs ThirdPartyCSRResponse struct { CSRs []CertSigningRequest `json:"csrs"` diff --git a/pkg/datastream/ds.go b/pkg/datastream/ds.go index e89ceca2..ccc9187f 100644 --- a/pkg/datastream/ds.go +++ b/pkg/datastream/ds.go @@ -4,9 +4,10 @@ package datastream import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -17,9 +18,61 @@ var ( type ( // DS is the ds api interface DS interface { - Activation - Properties - Stream + // Activation + + // ActivateStream activates stream with given ID. + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/put-stream-activate + ActivateStream(context.Context, ActivateStreamRequest) (*DetailedStreamVersion, error) + + // DeactivateStream deactivates stream with given ID. + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/put-stream-deactivate + DeactivateStream(context.Context, DeactivateStreamRequest) (*DetailedStreamVersion, error) + + // GetActivationHistory returns a history of activation status changes for all versions of a stream. + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/get-stream-activation-history + GetActivationHistory(context.Context, GetActivationHistoryRequest) ([]ActivationHistoryEntry, error) + + // Properties + + // GetProperties returns properties that are active on the production and staging network for a specific product type that are available within a group + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/get-group-properties + GetProperties(context.Context, GetPropertiesRequest) (*PropertiesDetails, error) + + // GetDatasetFields returns groups of data set fields available in the template. + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/get-dataset-fields + GetDatasetFields(context.Context, GetDatasetFieldsRequest) (*DataSets, error) + + // Stream + + // CreateStream creates a stream + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/post-stream + CreateStream(context.Context, CreateStreamRequest) (*DetailedStreamVersion, error) + + // GetStream gets stream details + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/get-stream + GetStream(context.Context, GetStreamRequest) (*DetailedStreamVersion, error) + + // UpdateStream updates a stream + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/put-stream + UpdateStream(context.Context, UpdateStreamRequest) (*DetailedStreamVersion, error) + + // DeleteStream deletes a stream + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/delete-stream + DeleteStream(context.Context, DeleteStreamRequest) error + + // ListStreams retrieves list of streams + // + // See: https://techdocs.akamai.com/datastream2/v2/reference/get-streams + ListStreams(context.Context, ListStreamsRequest) ([]StreamDetails, error) } ds struct { diff --git a/pkg/datastream/ds_test.go b/pkg/datastream/ds_test.go index 495df802..e879b5c5 100644 --- a/pkg/datastream/ds_test.go +++ b/pkg/datastream/ds_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/datastream/errors.go b/pkg/datastream/errors.go index 97d30292..f132ce6b 100644 --- a/pkg/datastream/errors.go +++ b/pkg/datastream/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/datastream/errors_test.go b/pkg/datastream/errors_test.go index 98e99a1f..13ee2e1d 100644 --- a/pkg/datastream/errors_test.go +++ b/pkg/datastream/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/datastream/mocks.go b/pkg/datastream/mocks.go index 8310e3bf..a7542ef9 100644 --- a/pkg/datastream/mocks.go +++ b/pkg/datastream/mocks.go @@ -12,7 +12,7 @@ type Mock struct { mock.Mock } -var _ Stream = &Mock{} +var _ DS = &Mock{} func (m *Mock) CreateStream(ctx context.Context, r CreateStreamRequest) (*DetailedStreamVersion, error) { args := m.Called(ctx, r) diff --git a/pkg/datastream/properties.go b/pkg/datastream/properties.go index f916ef26..c3aa9019 100644 --- a/pkg/datastream/properties.go +++ b/pkg/datastream/properties.go @@ -11,19 +11,6 @@ import ( ) type ( - // Properties is an interface for listing various DS API properties - Properties interface { - // GetProperties returns properties that are active on the production and staging network for a specific product type that are available within a group - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/get-group-properties - GetProperties(context.Context, GetPropertiesRequest) (*PropertiesDetails, error) - - // GetDatasetFields returns groups of data set fields available in the template. - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/get-dataset-fields - GetDatasetFields(context.Context, GetDatasetFieldsRequest) (*DataSets, error) - } - // GetPropertiesRequest contains parameters necessary to send a GetProperties request GetPropertiesRequest struct { GroupId int diff --git a/pkg/datastream/properties_test.go b/pkg/datastream/properties_test.go index 5542f767..0557829b 100644 --- a/pkg/datastream/properties_test.go +++ b/pkg/datastream/properties_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -246,7 +246,7 @@ func TestDs_GetDatasetFields(t *testing.T) { }, }, "validation error - invalid product id": { - request: GetDatasetFieldsRequest{ProductID: tools.StringPtr("INVALID_PROD_ID")}, + request: GetDatasetFieldsRequest{ProductID: ptr.To("INVALID_PROD_ID")}, responseStatus: http.StatusBadRequest, responseBody: ` { diff --git a/pkg/datastream/stream.go b/pkg/datastream/stream.go index a68958db..3e62aff8 100644 --- a/pkg/datastream/stream.go +++ b/pkg/datastream/stream.go @@ -12,34 +12,6 @@ import ( ) type ( - // Stream is a ds stream operations API interface - Stream interface { - // CreateStream creates a stream - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/post-stream - CreateStream(context.Context, CreateStreamRequest) (*DetailedStreamVersion, error) - - // GetStream gets stream details - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/get-stream - GetStream(context.Context, GetStreamRequest) (*DetailedStreamVersion, error) - - // UpdateStream updates a stream - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/put-stream - UpdateStream(context.Context, UpdateStreamRequest) (*DetailedStreamVersion, error) - - // DeleteStream deletes a stream - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/delete-stream - DeleteStream(context.Context, DeleteStreamRequest) error - - // ListStreams retrieves list of streams - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/get-streams - ListStreams(context.Context, ListStreamsRequest) ([]StreamDetails, error) - } - // DetailedStreamVersion is returned from GetStream DetailedStreamVersion struct { ContractID string `json:"contractId"` diff --git a/pkg/datastream/stream_activation.go b/pkg/datastream/stream_activation.go index 566363a9..9d0fc3f5 100644 --- a/pkg/datastream/stream_activation.go +++ b/pkg/datastream/stream_activation.go @@ -11,24 +11,6 @@ import ( ) type ( - // Activation is a ds stream activations API interface. - Activation interface { - // ActivateStream activates stream with given ID. - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/put-stream-activate - ActivateStream(context.Context, ActivateStreamRequest) (*DetailedStreamVersion, error) - - // DeactivateStream deactivates stream with given ID. - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/put-stream-deactivate - DeactivateStream(context.Context, DeactivateStreamRequest) (*DetailedStreamVersion, error) - - // GetActivationHistory returns a history of activation status changes for all versions of a stream. - // - // See: https://techdocs.akamai.com/datastream2/v2/reference/get-stream-activation-history - GetActivationHistory(context.Context, GetActivationHistoryRequest) ([]ActivationHistoryEntry, error) - } - // ActivationHistoryEntry contains single ActivationHistory item ActivationHistoryEntry struct { ModifiedBy string `json:"modifiedBy"` diff --git a/pkg/datastream/stream_test.go b/pkg/datastream/stream_test.go index 63d7731a..9291abc1 100644 --- a/pkg/datastream/stream_test.go +++ b/pkg/datastream/stream_test.go @@ -9,7 +9,7 @@ import ( "reflect" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1547,7 +1547,7 @@ func TestDs_ListStreams(t *testing.T) { }, "200 OK - with groupId": { request: ListStreamsRequest{ - GroupID: tools.IntPtr(1234), + GroupID: ptr.To(1234), }, responseStatus: http.StatusOK, responseBody: ` diff --git a/pkg/dns/authorities.go b/pkg/dns/authorities.go index 58c2de17..80cfcca1 100644 --- a/pkg/dns/authorities.go +++ b/pkg/dns/authorities.go @@ -2,51 +2,77 @@ package dns import ( "context" + "errors" "fmt" "net/http" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Authorities contains operations available on Authorities data sources. - Authorities interface { - // GetAuthorities provides a list of structured read-only list of name servers. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-data-authorities - GetAuthorities(context.Context, string) (*AuthorityResponse, error) - // GetNameServerRecordList provides a list of name server records. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-data-authorities - GetNameServerRecordList(context.Context, string) ([]string, error) - } - // Contract contains contractID and a list of currently assigned Akamai authoritative nameservers Contract struct { ContractID string `json:"contractId"` Authorities []string `json:"authorities"` } - // AuthorityResponse contains response with a list of one or more Contracts AuthorityResponse struct { Contracts []Contract `json:"contracts"` } + // GetAuthoritiesRequest contains request parameters for GetAuthorities + GetAuthoritiesRequest struct { + ContractIDs string + } + + // GetAuthoritiesResponse contains the response data from GetAuthorities operation + GetAuthoritiesResponse struct { + Contracts []Contract `json:"contracts"` + } + + // GetNameServerRecordListRequest contains request parameters for GetNameServerRecordList + GetNameServerRecordListRequest struct { + ContractIDs string + } +) + +var ( + // ErrGetAuthorities is returned when GetAuthorities fails + ErrGetAuthorities = errors.New("get authorities") + // ErrGetNameServerRecordList is returned when GetNameServerRecordList fails + ErrGetNameServerRecordList = errors.New("get name server record list") ) -func (d *dns) GetAuthorities(ctx context.Context, contractID string) (*AuthorityResponse, error) { +// Validate validates GetAuthoritiesRequest +func (r GetAuthoritiesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "ContractIDs": validation.Validate(r.ContractIDs, validation.Required), + }) +} + +// Validate validates GetNameServerRecordListRequest +func (r GetNameServerRecordListRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "ContractIDs": validation.Validate(r.ContractIDs, validation.Required), + }) +} + +func (d *dns) GetAuthorities(ctx context.Context, params GetAuthoritiesRequest) (*GetAuthoritiesResponse, error) { logger := d.Log(ctx) logger.Debug("GetAuthorities") - if contractID == "" { - return nil, fmt.Errorf("%w: GetAuthorities reqs valid contractId", ErrBadRequest) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetAuthorities, ErrStructValidation, err) } - getURL := fmt.Sprintf("/config-dns/v2/data/authorities?contractIds=%s", contractID) + getURL := fmt.Sprintf("/config-dns/v2/data/authorities?contractIds=%s", params.ContractIDs) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create getauthorities request: %w", err) } - var result AuthorityResponse + var result GetAuthoritiesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetAuthorities request failed: %w", err) @@ -59,15 +85,15 @@ func (d *dns) GetAuthorities(ctx context.Context, contractID string) (*Authority return &result, nil } -func (d *dns) GetNameServerRecordList(ctx context.Context, contractID string) ([]string, error) { +func (d *dns) GetNameServerRecordList(ctx context.Context, params GetNameServerRecordListRequest) ([]string, error) { logger := d.Log(ctx) logger.Debug("GetNameServerRecordList") - if contractID == "" { - return nil, fmt.Errorf("%w: GetAuthorities requires valid contractId", ErrBadRequest) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetNameServerRecordList, ErrStructValidation, err) } - NSrecords, err := d.GetAuthorities(ctx, contractID) + NSrecords, err := d.GetAuthorities(ctx, GetAuthoritiesRequest{ContractIDs: params.ContractIDs}) if err != nil { return nil, err } diff --git a/pkg/dns/authorities_test.go b/pkg/dns/authorities_test.go index d6ef7596..b06de3a1 100644 --- a/pkg/dns/authorities_test.go +++ b/pkg/dns/authorities_test.go @@ -13,15 +13,15 @@ import ( func TestDNS_GetAuthorities(t *testing.T) { tests := map[string]struct { - contractID string + params GetAuthoritiesRequest responseStatus int responseBody string expectedPath string - expectedResponse *AuthorityResponse - withError error + expectedResponse *GetAuthoritiesResponse + withError func(*testing.T, error) }{ "200 OK": { - contractID: "9-9XXXXX", + params: GetAuthoritiesRequest{ContractIDs: "9-9XXXXX"}, responseStatus: http.StatusOK, responseBody: ` { @@ -40,7 +40,7 @@ func TestDNS_GetAuthorities(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/data/authorities?contractIds=9-9XXXXX", - expectedResponse: &AuthorityResponse{ + expectedResponse: &GetAuthoritiesResponse{ Contracts: []Contract{ { ContractID: "9-9XXXXX", @@ -57,14 +57,13 @@ func TestDNS_GetAuthorities(t *testing.T) { }, }, "Missing arguments": { - contractID: "", responseStatus: http.StatusOK, - responseBody: "", - expectedPath: "/config-dns/v2/data/authorities?contractIds=9-9XXXXX", - withError: ErrBadRequest, + withError: func(t *testing.T, err error) { + assert.Equal(t, "get authorities: struct validation: ContractIDs: cannot be blank", err.Error()) + }, }, "500 internal server error": { - contractID: "9-9XXXXX", + params: GetAuthoritiesRequest{ContractIDs: "9-9XXXXX"}, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -74,11 +73,14 @@ func TestDNS_GetAuthorities(t *testing.T) { "status": 500 }`, expectedPath: "/config-dns/v2/data/authorities?contractIds=9-9XXXXX", - withError: &Error{ - Type: "internal_error", - Title: "Internal Server Error", - Detail: "Error fetching authorities", - StatusCode: http.StatusInternalServerError, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error fetching authorities", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) }, }, } @@ -93,9 +95,9 @@ func TestDNS_GetAuthorities(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetAuthorities(context.Background(), test.contractID) + result, err := client.GetAuthorities(context.Background(), test.params) if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + test.withError(t, err) return } require.NoError(t, err) @@ -106,15 +108,15 @@ func TestDNS_GetAuthorities(t *testing.T) { func TestDNS_GetNameServerRecordList(t *testing.T) { tests := map[string]struct { - contractID string + params GetNameServerRecordListRequest responseStatus int responseBody string expectedPath string expectedResponse []string - withError error + withError func(*testing.T, error) }{ "test with valid arguments": { - contractID: "9-9XXXXX", + params: GetNameServerRecordListRequest{ContractIDs: "9-9XXXXX"}, responseStatus: http.StatusOK, responseBody: ` { @@ -136,9 +138,10 @@ func TestDNS_GetNameServerRecordList(t *testing.T) { expectedPath: "/config-dns/v2/data/authorities?contractIds=9-9XXXXX", }, "test with missing arguments": { - contractID: "", expectedPath: "/config-dns/v2/data/authorities?contractIds=9-9XXXXX", - withError: ErrBadRequest, + withError: func(t *testing.T, err error) { + assert.Equal(t, "get name server record list: struct validation: ContractIDs: cannot be blank", err.Error()) + }, }, } @@ -152,9 +155,9 @@ func TestDNS_GetNameServerRecordList(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetNameServerRecordList(context.Background(), test.contractID) + result, err := client.GetNameServerRecordList(context.Background(), test.params) if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + test.withError(t, err) return } require.NoError(t, err) diff --git a/pkg/dns/data.go b/pkg/dns/data.go index 61744f85..87b573cb 100644 --- a/pkg/dns/data.go +++ b/pkg/dns/data.go @@ -9,14 +9,6 @@ import ( ) type ( - // Data contains operations available on Data resources. - Data interface { - // ListGroups returns group list associated with particular user - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-data-groups - ListGroups(context.Context, ListGroupRequest) (*ListGroupResponse, error) - } - // ListGroupResponse lists the groups accessible to the current user ListGroupResponse struct { Groups []Group `json:"groups"` diff --git a/pkg/dns/data_test.go b/pkg/dns/data_test.go index f233e8bf..757aa0e3 100644 --- a/pkg/dns/data_test.go +++ b/pkg/dns/data_test.go @@ -18,7 +18,7 @@ func TestDNS_ListGroups(t *testing.T) { responseBody string expectedPath string expectedResponse *ListGroupResponse - withError error + withError func(*testing.T, error) }{ "200 OK, when optional query parameter provided": { request: ListGroupRequest{ @@ -137,11 +137,14 @@ func TestDNS_ListGroups(t *testing.T) { "status": 500 }`, expectedPath: "/config-dns/v2/data/groups/", - withError: &Error{ - Type: "internal_error", - Title: "Internal Server Error", - Detail: "Error fetching authorities", - StatusCode: http.StatusInternalServerError, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error fetching authorities", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) }, }, } @@ -158,7 +161,7 @@ func TestDNS_ListGroups(t *testing.T) { client := mockAPIClient(t, mockServer) result, err := client.ListGroups(context.Background(), test.request) if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + test.withError(t, err) return } require.NoError(t, err) diff --git a/pkg/dns/dns.go b/pkg/dns/dns.go index bbb72ac5..0bf55369 100644 --- a/pkg/dns/dns.go +++ b/pkg/dns/dns.go @@ -4,26 +4,193 @@ package dns import ( + "context" "errors" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( - // ErrStructValidation is returned when given struct validation failed + // ErrStructValidation is returned when given struct validation failed. ErrStructValidation = errors.New("struct validation") ) type ( // DNS is the dns api interface DNS interface { - Authorities - Data - Recordsets - Records - TSIGKeys - Zones + // Authorities + + // GetAuthorities provides a list of structured read-only list of name servers. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-data-authorities + GetAuthorities(context.Context, GetAuthoritiesRequest) (*GetAuthoritiesResponse, error) + + // GetNameServerRecordList provides a list of name server records. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-data-authorities + GetNameServerRecordList(context.Context, GetNameServerRecordListRequest) ([]string, error) + + // ListGroups returns group list associated with particular user + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-data-groups + ListGroups(context.Context, ListGroupRequest) (*ListGroupResponse, error) + + // Data + + // GetRdata retrieves record rdata, e.g. target. + GetRdata(ctx context.Context, params GetRdataRequest) ([]string, error) + // ProcessRdata process rdata. + ProcessRdata(context.Context, []string, string) []string + // ParseRData parses rdata. returning map. + ParseRData(context.Context, string, []string) map[string]interface{} + + // Recordsets + + // GetRecordSets retrieves record sets with Query Args. No formatting of arg values. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-recordsets + GetRecordSets(context.Context, GetRecordSetsRequest) (*GetRecordSetsResponse, error) + // CreateRecordSets creates multiple record sets. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-recordsets + CreateRecordSets(context.Context, CreateRecordSetsRequest) error + // UpdateRecordSets replaces list of record sets. + // + // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-recordsets + UpdateRecordSets(context.Context, UpdateRecordSetsRequest) error + + // GetRecordList retrieves recordset list based on type. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-recordsets + GetRecordList(context.Context, GetRecordListRequest) (*GetRecordListResponse, error) + + // Records + + // GetRecord retrieves a recordset and returns as RecordBody. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-name-type + GetRecord(context.Context, GetRecordRequest) (*GetRecordResponse, error) + // CreateRecord creates recordset. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-names-name-types-type + CreateRecord(context.Context, CreateRecordRequest) error + // DeleteRecord removes recordset. + // + // See: https://techdocs.akamai.com/edge-dns/reference/delete-zone-name-type + DeleteRecord(context.Context, DeleteRecordRequest) error + // UpdateRecord replaces the recordset. + // + // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-names-name-types-type + UpdateRecord(context.Context, UpdateRecordRequest) error + + // TSIGKeys + + // ListTSIGKeys lists the TSIG keys used by zones that you are allowed to manage. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-keys + ListTSIGKeys(context.Context, ListTSIGKeysRequest) (*ListTSIGKeysResponse, error) + // GetTSIGKeyZones retrieves DNS Zones using TSIG key. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-keys-used-by + GetTSIGKeyZones(context.Context, GetTSIGKeyZonesRequest) (*GetTSIGKeyZonesResponse, error) + // GetTSIGKeyAliases retrieves a DNS Zone's aliases. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-key-used-by + GetTSIGKeyAliases(context.Context, GetTSIGKeyAliasesRequest) (*GetTSIGKeyAliasesResponse, error) + // UpdateTSIGKeyBulk updates Bulk Zones TSIG key. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-keys-bulk-update + UpdateTSIGKeyBulk(context.Context, UpdateTSIGKeyBulkRequest) error + // GetTSIGKey retrieves a TSIG key for zone. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-key + GetTSIGKey(context.Context, GetTSIGKeyRequest) (*GetTSIGKeyResponse, error) + // DeleteTSIGKey deletes TSIG key for zone. + // + // See: https://techdocs.akamai.com/edge-dns/reference/delete-zones-zone-key + DeleteTSIGKey(context.Context, DeleteTSIGKeyRequest) error + // UpdateTSIGKey updates TSIG key for zone. + // + // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-key + UpdateTSIGKey(context.Context, UpdateTSIGKeyRequest) error + + // Zones + + // ListZones retrieves a list of all zones user can access. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones + ListZones(context.Context, ListZonesRequest) (*ZoneListResponse, error) + + // GetZone retrieves Zone metadata. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zone + GetZone(context.Context, GetZoneRequest) (*GetZoneResponse, error) + //GetChangeList retrieves Zone changelist. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-changelists-zone + GetChangeList(context.Context, GetChangeListRequest) (*GetChangeListResponse, error) + // GetMasterZoneFile retrieves master zone file. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-zone-file + GetMasterZoneFile(context.Context, GetMasterZoneFileRequest) (string, error) + // PostMasterZoneFile updates master zone file. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-zone-file + PostMasterZoneFile(context.Context, PostMasterZoneFileRequest) error + // CreateZone creates new zone. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zone + CreateZone(context.Context, CreateZoneRequest) error + // SaveChangeList creates a new Change List based on the most recent version of a zone. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-changelists + SaveChangeList(context.Context, SaveChangeListRequest) error + // SubmitChangeList submits changelist for the Zone to create default NS SOA records. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-changelists-zone-submit + SubmitChangeList(context.Context, SubmitChangeListRequest) error + // UpdateZone updates zone. + // + // See: https://techdocs.akamai.com/edge-dns/reference/put-zone + UpdateZone(context.Context, UpdateZoneRequest) error + + // GetZoneNames retrieves a list of a zone's record names. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-names + GetZoneNames(context.Context, GetZoneNamesRequest) (*GetZoneNamesResponse, error) + // GetZoneNameTypes retrieves a zone name's record types. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-name-types + GetZoneNameTypes(context.Context, GetZoneNameTypesRequest) (*GetZoneNameTypesResponse, error) + // CreateBulkZones submits create bulk zone request. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-create-requests + CreateBulkZones(context.Context, CreateBulkZonesRequest) (*CreateBulkZonesResponse, error) + // DeleteBulkZones submits delete bulk zone request. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-delete-requests + DeleteBulkZones(context.Context, DeleteBulkZonesRequest) (*DeleteBulkZonesResponse, error) + // GetBulkZoneCreateStatus retrieves submit request status. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-create-requests-requestid + GetBulkZoneCreateStatus(context.Context, GetBulkZoneCreateStatusRequest) (*GetBulkZoneCreateStatusResponse, error) + //GetBulkZoneDeleteStatus retrieves submit request status. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-delete-requests-requestid + GetBulkZoneDeleteStatus(context.Context, GetBulkZoneDeleteStatusRequest) (*GetBulkZoneDeleteStatusResponse, error) + // GetBulkZoneCreateResult retrieves create request result. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-create-requests-requestid-result + GetBulkZoneCreateResult(ctx context.Context, request GetBulkZoneCreateResultRequest) (*GetBulkZoneCreateResultResponse, error) + // GetBulkZoneDeleteResult retrieves delete request result. + // + // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-delete-requests-requestid-result + GetBulkZoneDeleteResult(context.Context, GetBulkZoneDeleteResultRequest) (*GetBulkZoneDeleteResultResponse, error) + // GetZonesDNSSecStatus returns the current DNSSEC status for one or more zones. + // + // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-dns-sec-status + GetZonesDNSSecStatus(context.Context, GetZonesDNSSecStatusRequest) (*GetZonesDNSSecStatusResponse, error) } dns struct { diff --git a/pkg/dns/dns_test.go b/pkg/dns/dns_test.go index f839d33f..241221f4 100644 --- a/pkg/dns/dns_test.go +++ b/pkg/dns/dns_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/dns/errors.go b/pkg/dns/errors.go index a82ebf23..926c10dd 100644 --- a/pkg/dns/errors.go +++ b/pkg/dns/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) var ( diff --git a/pkg/dns/errors_test.go b/pkg/dns/errors_test.go index d3c41c8d..9cf796ba 100644 --- a/pkg/dns/errors_test.go +++ b/pkg/dns/errors_test.go @@ -7,9 +7,8 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" - "github.com/tj/assert" ) diff --git a/pkg/dns/mocks.go b/pkg/dns/mocks.go index e9998be0..7bbb0393 100644 --- a/pkg/dns/mocks.go +++ b/pkg/dns/mocks.go @@ -14,14 +14,8 @@ type Mock struct { var _ DNS = &Mock{} -func (d *Mock) ListZones(ctx context.Context, query ...ZoneListQueryArgs) (*ZoneListResponse, error) { - var args mock.Arguments - - if len(query) > 0 { - args = d.Called(ctx, query[0]) - } else { - args = d.Called(ctx) - } +func (d *Mock) ListZones(ctx context.Context, req ListZonesRequest) (*ZoneListResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) @@ -40,156 +34,145 @@ func (d *Mock) GetZonesDNSSecStatus(ctx context.Context, params GetZonesDNSSecSt return args.Get(0).(*GetZonesDNSSecStatusResponse), args.Error(1) } -func (d *Mock) GetZone(ctx context.Context, name string) (*ZoneResponse, error) { - args := d.Called(ctx, name) +func (d *Mock) GetZone(ctx context.Context, req GetZoneRequest) (*GetZoneResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ZoneResponse), args.Error(1) + return args.Get(0).(*GetZoneResponse), args.Error(1) } -func (d *Mock) GetChangeList(ctx context.Context, param string) (*ChangeListResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetChangeList(ctx context.Context, req GetChangeListRequest) (*GetChangeListResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ChangeListResponse), args.Error(1) + return args.Get(0).(*GetChangeListResponse), args.Error(1) } -func (d *Mock) GetMasterZoneFile(ctx context.Context, param string) (string, error) { - args := d.Called(ctx, param) +func (d *Mock) GetMasterZoneFile(ctx context.Context, req GetMasterZoneFileRequest) (string, error) { + args := d.Called(ctx, req) return args.String(0), args.Error(1) } -func (d *Mock) CreateZone(ctx context.Context, param1 *ZoneCreate, param2 ZoneQueryString, param3 ...bool) error { +func (d *Mock) CreateZone(ctx context.Context, req CreateZoneRequest) error { var args mock.Arguments - - if len(param3) > 0 { - args = d.Called(ctx, param1, param2, param3[0]) - } else { - args = d.Called(ctx, param1, param2) - } + args = d.Called(ctx, req) return args.Error(0) } -func (d *Mock) SaveChangelist(ctx context.Context, param *ZoneCreate) error { - args := d.Called(ctx, param) +func (d *Mock) SaveChangeList(ctx context.Context, req SaveChangeListRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) SubmitChangelist(ctx context.Context, param *ZoneCreate) error { - args := d.Called(ctx, param) +func (d *Mock) SubmitChangeList(ctx context.Context, req SubmitChangeListRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) UpdateZone(ctx context.Context, param1 *ZoneCreate, param2 ZoneQueryString) error { - args := d.Called(ctx, param1, param2) +func (d *Mock) UpdateZone(ctx context.Context, req UpdateZoneRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) DeleteZone(ctx context.Context, param1 *ZoneCreate, param2 ZoneQueryString) error { - args := d.Called(ctx, param1, param2) - - return args.Error(0) -} - -func (d *Mock) GetZoneNames(ctx context.Context, param string) (*ZoneNamesResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetZoneNames(ctx context.Context, req GetZoneNamesRequest) (*GetZoneNamesResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ZoneNamesResponse), args.Error(1) + return args.Get(0).(*GetZoneNamesResponse), args.Error(1) } -func (d *Mock) GetZoneNameTypes(ctx context.Context, param1 string, param2 string) (*ZoneNameTypesResponse, error) { - args := d.Called(ctx, param1, param2) +func (d *Mock) GetZoneNameTypes(ctx context.Context, req GetZoneNameTypesRequest) (*GetZoneNameTypesResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ZoneNameTypesResponse), args.Error(1) + return args.Get(0).(*GetZoneNameTypesResponse), args.Error(1) } -func (d *Mock) ListTSIGKeys(ctx context.Context, param *TSIGQueryString) (*TSIGReportResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) ListTSIGKeys(ctx context.Context, req ListTSIGKeysRequest) (*ListTSIGKeysResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*TSIGReportResponse), args.Error(1) + return args.Get(0).(*ListTSIGKeysResponse), args.Error(1) } -func (d *Mock) GetTSIGKeyZones(ctx context.Context, param *TSIGKey) (*ZoneNameListResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetTSIGKeyZones(ctx context.Context, req GetTSIGKeyZonesRequest) (*GetTSIGKeyZonesResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ZoneNameListResponse), args.Error(1) + return args.Get(0).(*GetTSIGKeyZonesResponse), args.Error(1) } -func (d *Mock) GetTSIGKeyAliases(ctx context.Context, param string) (*ZoneNameListResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetTSIGKeyAliases(ctx context.Context, req GetTSIGKeyAliasesRequest) (*GetTSIGKeyAliasesResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ZoneNameListResponse), args.Error(1) + return args.Get(0).(*GetTSIGKeyAliasesResponse), args.Error(1) } -func (d *Mock) TSIGKeyBulkUpdate(ctx context.Context, param1 *TSIGKeyBulkPost) error { - args := d.Called(ctx, param1) +func (d *Mock) UpdateTSIGKeyBulk(ctx context.Context, req UpdateTSIGKeyBulkRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) GetTSIGKey(ctx context.Context, param string) (*TSIGKeyResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetTSIGKey(ctx context.Context, req GetTSIGKeyRequest) (*GetTSIGKeyResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*TSIGKeyResponse), args.Error(1) + return args.Get(0).(*GetTSIGKeyResponse), args.Error(1) } -func (d *Mock) DeleteTSIGKey(ctx context.Context, param1 string) error { - args := d.Called(ctx, param1) +func (d *Mock) DeleteTSIGKey(ctx context.Context, req DeleteTSIGKeyRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) UpdateTSIGKey(ctx context.Context, param1 *TSIGKey, param2 string) error { - args := d.Called(ctx, param1, param2) +func (d *Mock) UpdateTSIGKey(ctx context.Context, req UpdateTSIGKeyRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) GetAuthorities(ctx context.Context, param string) (*AuthorityResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetAuthorities(ctx context.Context, req GetAuthoritiesRequest) (*GetAuthoritiesResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*AuthorityResponse), args.Error(1) + return args.Get(0).(*GetAuthoritiesResponse), args.Error(1) } -func (d *Mock) GetNameServerRecordList(ctx context.Context, param string) ([]string, error) { - args := d.Called(ctx, param) +func (d *Mock) GetNameServerRecordList(ctx context.Context, req GetNameServerRecordListRequest) ([]string, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) @@ -198,18 +181,18 @@ func (d *Mock) GetNameServerRecordList(ctx context.Context, param string) ([]str return args.Get(0).([]string), args.Error(1) } -func (d *Mock) GetRecordList(ctx context.Context, param string, param2 string, param3 string) (*RecordSetResponse, error) { - args := d.Called(ctx, param, param2, param3) +func (d *Mock) GetRecordList(ctx context.Context, req GetRecordListRequest) (*GetRecordListResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*RecordSetResponse), args.Error(1) + return args.Get(0).(*GetRecordListResponse), args.Error(1) } -func (d *Mock) GetRdata(ctx context.Context, param string, param2 string, param3 string) ([]string, error) { - args := d.Called(ctx, param, param2, param3) +func (d *Mock) GetRdata(ctx context.Context, req GetRdataRequest) ([]string, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) @@ -234,154 +217,119 @@ func (d *Mock) ParseRData(ctx context.Context, param string, param2 []string) ma return args.Get(0).(map[string]interface{}) } -func (d *Mock) GetRecord(ctx context.Context, param string, param2 string, param3 string) (*RecordBody, error) { - args := d.Called(ctx, param, param2, param3) +func (d *Mock) GetRecord(ctx context.Context, req GetRecordRequest) (*GetRecordResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*RecordBody), args.Error(1) + return args.Get(0).(*GetRecordResponse), args.Error(1) } -func (d *Mock) CreateRecord(ctx context.Context, param *RecordBody, param2 string, param3 ...bool) error { +func (d *Mock) CreateRecord(ctx context.Context, req CreateRecordRequest) error { var args mock.Arguments - - if len(param3) > 0 { - args = d.Called(ctx, param, param2, param3) - } else { - args = d.Called(ctx, param, param2) - } + args = d.Called(ctx, req) return args.Error(0) } -func (d *Mock) DeleteRecord(ctx context.Context, param *RecordBody, param2 string, param3 ...bool) error { +func (d *Mock) DeleteRecord(ctx context.Context, req DeleteRecordRequest) error { var args mock.Arguments - - if len(param3) > 0 { - args = d.Called(ctx, param, param2, param3) - } else { - args = d.Called(ctx, param, param2) - } + args = d.Called(ctx, req) return args.Error(0) } -func (d *Mock) UpdateRecord(ctx context.Context, param *RecordBody, param2 string, param3 ...bool) error { +func (d *Mock) UpdateRecord(ctx context.Context, req UpdateRecordRequest) error { var args mock.Arguments - - if len(param3) > 0 { - args = d.Called(ctx, param, param2, param3) - } else { - args = d.Called(ctx, param, param2) - } + args = d.Called(ctx, req) return args.Error(0) } -func (d *Mock) GetRecordSets(ctx context.Context, param string, param2 ...RecordSetQueryArgs) (*RecordSetResponse, error) { +func (d *Mock) GetRecordSets(ctx context.Context, req GetRecordSetsRequest) (*GetRecordSetsResponse, error) { var args mock.Arguments - - if len(param2) > 0 { - args = d.Called(ctx, param, param2) - } else { - args = d.Called(ctx, param) - } + args = d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*RecordSetResponse), args.Error(1) + return args.Get(0).(*GetRecordSetsResponse), args.Error(1) } -func (d *Mock) CreateRecordSets(ctx context.Context, param *RecordSets, param2 string, param3 ...bool) error { +func (d *Mock) CreateRecordSets(ctx context.Context, req CreateRecordSetsRequest) error { var args mock.Arguments - - if len(param3) > 0 { - args = d.Called(ctx, param, param2, param3) - } else { - args = d.Called(ctx, param, param2) - } + args = d.Called(ctx, req) return args.Error(0) } -func (d *Mock) UpdateRecordSets(ctx context.Context, param *RecordSets, param2 string, param3 ...bool) error { +func (d *Mock) UpdateRecordSets(ctx context.Context, req UpdateRecordSetsRequest) error { var args mock.Arguments - - if len(param3) > 0 { - args = d.Called(ctx, param, param2, param3) - } else { - args = d.Called(ctx, param, param2) - } + args = d.Called(ctx, req) return args.Error(0) } -func (d *Mock) PostMasterZoneFile(ctx context.Context, param string, param2 string) error { - args := d.Called(ctx, param, param2) +func (d *Mock) PostMasterZoneFile(ctx context.Context, req PostMasterZoneFileRequest) error { + args := d.Called(ctx, req) return args.Error(0) } -func (d *Mock) CreateBulkZones(ctx context.Context, param *BulkZonesCreate, param2 ZoneQueryString) (*BulkZonesResponse, error) { - args := d.Called(ctx, param, param2) +func (d *Mock) CreateBulkZones(ctx context.Context, req CreateBulkZonesRequest) (*CreateBulkZonesResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*BulkZonesResponse), args.Error(1) + return args.Get(0).(*CreateBulkZonesResponse), args.Error(1) } -func (d *Mock) DeleteBulkZones(ctx context.Context, param *ZoneNameListResponse, param2 ...bool) (*BulkZonesResponse, error) { +func (d *Mock) DeleteBulkZones(ctx context.Context, req DeleteBulkZonesRequest) (*DeleteBulkZonesResponse, error) { var args mock.Arguments - - if len(param2) > 0 { - args = d.Called(ctx, param, param2[0]) - } else { - args = d.Called(ctx, param) - } + args = d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*BulkZonesResponse), args.Error(1) + return args.Get(0).(*DeleteBulkZonesResponse), args.Error(1) } -func (d *Mock) GetBulkZoneCreateStatus(ctx context.Context, param string) (*BulkStatusResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetBulkZoneCreateStatus(ctx context.Context, req GetBulkZoneCreateStatusRequest) (*GetBulkZoneCreateStatusResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*BulkStatusResponse), args.Error(1) + return args.Get(0).(*GetBulkZoneCreateStatusResponse), args.Error(1) } -func (d *Mock) GetBulkZoneDeleteStatus(ctx context.Context, param string) (*BulkStatusResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetBulkZoneDeleteStatus(ctx context.Context, req GetBulkZoneDeleteStatusRequest) (*GetBulkZoneDeleteStatusResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*BulkStatusResponse), args.Error(1) + return args.Get(0).(*GetBulkZoneDeleteStatusResponse), args.Error(1) } -func (d *Mock) GetBulkZoneCreateResult(ctx context.Context, param string) (*BulkCreateResultResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetBulkZoneCreateResult(ctx context.Context, req GetBulkZoneCreateResultRequest) (*GetBulkZoneCreateResultResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*BulkCreateResultResponse), args.Error(1) + return args.Get(0).(*GetBulkZoneCreateResultResponse), args.Error(1) } -func (d *Mock) GetBulkZoneDeleteResult(ctx context.Context, param string) (*BulkDeleteResultResponse, error) { - args := d.Called(ctx, param) +func (d *Mock) GetBulkZoneDeleteResult(ctx context.Context, req GetBulkZoneDeleteResultRequest) (*GetBulkZoneDeleteResultResponse, error) { + args := d.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*BulkDeleteResultResponse), args.Error(1) + return args.Get(0).(*GetBulkZoneDeleteResultResponse), args.Error(1) } func (d *Mock) ListGroups(ctx context.Context, request ListGroupRequest) (*ListGroupResponse, error) { diff --git a/pkg/dns/record.go b/pkg/dns/record.go index cb72441f..225a34a5 100644 --- a/pkg/dns/record.go +++ b/pkg/dns/record.go @@ -2,55 +2,84 @@ package dns import ( "context" + "errors" "fmt" "net/http" - "sync" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) -// Records contains operations available on a Record resource. -type Records interface { - // GetRecordList retrieves recordset list based on type. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-recordsets - GetRecordList(context.Context, string, string, string) (*RecordSetResponse, error) - // GetRdata retrieves record rdata, e.g. target. - GetRdata(context.Context, string, string, string) ([]string, error) - // ProcessRdata process rdata. - ProcessRdata(context.Context, []string, string) []string - // ParseRData parses rdata. returning map. - ParseRData(context.Context, string, []string) map[string]interface{} - // GetRecord retrieves a recordset and returns as RecordBody. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-name-type - GetRecord(context.Context, string, string, string) (*RecordBody, error) - // CreateRecord creates recordset. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-names-name-types-type - CreateRecord(context.Context, *RecordBody, string, ...bool) error - // DeleteRecord removes recordset. - // - // See: https://techdocs.akamai.com/edge-dns/reference/delete-zone-name-type - DeleteRecord(context.Context, *RecordBody, string, ...bool) error - // UpdateRecord replaces the recordset. - // - // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-names-name-types-type - UpdateRecord(context.Context, *RecordBody, string, ...bool) error -} +type ( + // RecordBody contains request body for dns record + RecordBody struct { + Name string `json:"name,omitempty"` + RecordType string `json:"type,omitempty"` + TTL int `json:"ttl,omitempty"` + Active bool `json:"active,omitempty"` + Target []string `json:"rdata,omitempty"` + } -// RecordBody contains request body for dns record -type RecordBody struct { - Name string `json:"name,omitempty"` - RecordType string `json:"type,omitempty"` - TTL int `json:"ttl,omitempty"` - Active bool `json:"active,omitempty"` - Target []string `json:"rdata,omitempty"` -} + // RecordRequest contains request parameters + RecordRequest struct { + Record *RecordBody + Zone string + RecLock []bool + } + // CreateRecordRequest contains request parameters for CreateRecord + CreateRecordRequest RecordRequest + + // UpdateRecordRequest contains request parameters for UpdateRecord + UpdateRecordRequest RecordRequest + + // DeleteRecordRequest contains request parameters for DeleteRecord + DeleteRecordRequest struct { + Zone string + Name string + RecordType string + RecLock []bool + } +) var ( zoneRecordWriteLock sync.Mutex ) +var ( + // ErrCreateRecord is returned when CreateRecord fails + ErrCreateRecord = errors.New("create record") + // ErrUpdateRecord is returned when UpdateRecord fails + ErrUpdateRecord = errors.New("update record") + // ErrDeleteRecord is returned when UpdateRecord fails + ErrDeleteRecord = errors.New("delete record") +) + +// Validate validates CreateRecordRequest +func (r CreateRecordRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "Record": validation.Validate(r.Record, validation.Required), + }) +} + +// Validate validates UpdateRecordRequest +func (r UpdateRecordRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "Record": validation.Validate(r.Record, validation.Required), + }) +} + +// Validate validates DeleteRecordRequest +func (r DeleteRecordRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "Name": validation.Validate(r.Name, validation.Required), + "RecordType": validation.Validate(r.RecordType, validation.Required), + }) +} + // Validate validates RecordBody func (rec *RecordBody) Validate() error { if len(rec.Name) < 1 { @@ -79,32 +108,32 @@ func localLock(lockArg []bool) bool { return true } -func (d *dns) CreateRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error { +func (d *dns) CreateRecord(ctx context.Context, params CreateRecordRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone, // so we have to save just one request at a time to ensure this is always // incremented properly - if localLock(recLock) { + if localLock(params.RecLock) { zoneRecordWriteLock.Lock() defer zoneRecordWriteLock.Unlock() } logger := d.Log(ctx) logger.Debug("CreateRecord") - logger.Debugf("DNS Lib Create Record: [%v]", record) - if err := record.Validate(); err != nil { - logger.Errorf("Record content not valid: %w", err) - return fmt.Errorf("CreateRecord content not valid. [%w]", err) + logger.Debugf("DNS Lib Create Record: [%v]", params.Record) + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrCreateRecord, ErrStructValidation, err) } - reqBody, err := convertStructToReqBody(record) + reqBody, err := convertStructToReqBody(params.Record) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - postURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType) + postURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", params.Zone, + params.Record.Name, params.Record.RecordType) req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody) if err != nil { return fmt.Errorf("failed to create CreateRecord request: %w", err) @@ -122,32 +151,33 @@ func (d *dns) CreateRecord(ctx context.Context, record *RecordBody, zone string, return nil } -func (d *dns) UpdateRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error { +func (d *dns) UpdateRecord(ctx context.Context, params UpdateRecordRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone // so we have to save just one request at a time to ensure this is always // incremented properly - if localLock(recLock) { + if localLock(params.RecLock) { zoneRecordWriteLock.Lock() defer zoneRecordWriteLock.Unlock() } logger := d.Log(ctx) logger.Debug("UpdateRecord") - logger.Debugf("DNS Lib Update Record: [%v]", record) - if err := record.Validate(); err != nil { - logger.Errorf("Record content not valid: %s", err.Error()) - return fmt.Errorf("UpdateRecord content not valid. [%w]", err) + logger.Debugf("DNS Lib Update Record: [%v]", params.Record) + + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrUpdateRecord, ErrStructValidation, err) } - reqBody, err := convertStructToReqBody(record) + reqBody, err := convertStructToReqBody(params.Record) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - putURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType) + putURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", params.Zone, + params.Record.Name, params.Record.RecordType) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody) if err != nil { return fmt.Errorf("failed to create UpdateRecord request: %w", err) @@ -165,14 +195,14 @@ func (d *dns) UpdateRecord(ctx context.Context, record *RecordBody, zone string, return nil } -func (d *dns) DeleteRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error { +func (d *dns) DeleteRecord(ctx context.Context, params DeleteRecordRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone // so we have to save just one request at a time to ensure this is always // incremented properly - if localLock(recLock) { + if localLock(params.RecLock) { zoneRecordWriteLock.Lock() defer zoneRecordWriteLock.Unlock() } @@ -180,12 +210,12 @@ func (d *dns) DeleteRecord(ctx context.Context, record *RecordBody, zone string, logger := d.Log(ctx) logger.Debug("DeleteRecord") - if err := record.Validate(); err != nil { - logger.Errorf("Record content not valid: %w", err) - return fmt.Errorf("DeleteRecord content not valid. [%w]", err) + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrDeleteRecord, ErrStructValidation, err) } - deleteURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType) + deleteURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", params.Zone, + params.Name, params.RecordType) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, deleteURL, nil) if err != nil { return fmt.Errorf("failed to create DeleteRecord request: %w", err) diff --git a/pkg/dns/record_lookup.go b/pkg/dns/record_lookup.go index 2d52288a..05fe16ce 100644 --- a/pkg/dns/record_lookup.go +++ b/pkg/dns/record_lookup.go @@ -2,15 +2,87 @@ package dns import ( "context" - "fmt" - "net/http" - "encoding/hex" + "errors" + "fmt" "net" + "net/http" "strconv" "strings" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) +type ( + // RdataRequest contains request parameters + RdataRequest struct { + Zone string + Name string + RecordType string + } + + // GetRecordRequest contains request parameters for GetRecord + GetRecordRequest RdataRequest + + // GetRecordResponse contains the response data from GetRecord operation + GetRecordResponse struct { + Name string `json:"name"` + RecordType string `json:"type"` + TTL int `json:"ttl"` + Active bool `json:"active"` + Target []string `json:"rdata"` + } + + // GetRecordListRequest contains request parameters for GetRecordList + GetRecordListRequest struct { + Zone string + RecordType string + } + + // GetRecordListResponse contains the response data from GetRecordList operation + GetRecordListResponse struct { + Metadata Metadata `json:"metadata"` + RecordSets []RecordSet `json:"recordsets"` + } + + // GetRdataRequest contains request parameters for GetRdata + GetRdataRequest RdataRequest +) + +var ( + // ErrGetRecord is returned when GetRecord fails + ErrGetRecord = errors.New("get record") + // ErrGetRecordList is returned when GetRecordList fails + ErrGetRecordList = errors.New("get record list") +) + +// Validate validates GetRecordRequest +func (r GetRecordRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "Name": validation.Validate(r.Name, validation.Required), + "RecordType": validation.Validate(r.RecordType, validation.Required), + }) +} + +// Validate validates GetRdataRequest +func (r GetRdataRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "Name": validation.Validate(r.Name, validation.Required), + "RecordType": validation.Validate(r.RecordType, validation.Required), + }) +} + +// Validate validates GetRecordListRequest +func (r GetRecordListRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "RecordType": validation.Validate(r.RecordType, validation.Required), + }) +} + func fullIPv6(ip net.IP) string { dst := make([]byte, hex.EncodedLen(len(ip))) @@ -46,17 +118,21 @@ func padCoordinates(str string) string { return latd + " " + latm + " " + lats + " " + latDir + " " + longd + " " + longm + " " + longs + " " + longDir + " " + padValue(altitude) + "m " + padValue(size) + "m " + padValue(horizPrecision) + "m " + padValue(vertPrecision) + "m" } -func (d *dns) GetRecord(ctx context.Context, zone, name, recordType string) (*RecordBody, error) { +func (d *dns) GetRecord(ctx context.Context, params GetRecordRequest) (*GetRecordResponse, error) { logger := d.Log(ctx) logger.Debug("GetRecord") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, name, recordType) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetRecord, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", params.Zone, params.Name, params.RecordType) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetRecord request: %w", err) } - var result RecordBody + var result GetRecordResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetRecord request failed: %w", err) @@ -69,17 +145,21 @@ func (d *dns) GetRecord(ctx context.Context, zone, name, recordType string) (*Re return &result, nil } -func (d *dns) GetRecordList(ctx context.Context, zone, _, recordType string) (*RecordSetResponse, error) { +func (d *dns) GetRecordList(ctx context.Context, params GetRecordListRequest) (*GetRecordListResponse, error) { logger := d.Log(ctx) logger.Debug("GetRecordList") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets?types=%s&showAll=true", zone, recordType) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetRecordList, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets?types=%s&showAll=true", params.Zone, params.RecordType) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetRecordList request: %w", err) } - var result RecordSetResponse + var result GetRecordListResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetRecordList request failed: %w", err) @@ -92,26 +172,33 @@ func (d *dns) GetRecordList(ctx context.Context, zone, _, recordType string) (*R return &result, nil } -func (d *dns) GetRdata(ctx context.Context, zone, name, recordType string) ([]string, error) { +func (d *dns) GetRdata(ctx context.Context, params GetRdataRequest) ([]string, error) { logger := d.Log(ctx) logger.Debug("GetrData") - records, err := d.GetRecordList(ctx, zone, name, recordType) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetRecordList, ErrStructValidation, err) + } + + records, err := d.GetRecordList(ctx, GetRecordListRequest{ + Zone: params.Zone, + RecordType: params.RecordType, + }) if err != nil { return nil, err } var rData []string for _, r := range records.RecordSets { - if r.Name == name { + if r.Name == params.Name { for _, i := range r.Rdata { str := i - if recordType == "AAAA" { + if params.RecordType == "AAAA" { addr := net.ParseIP(str) result := fullIPv6(addr) str = result - } else if recordType == "LOC" { + } else if params.RecordType == "LOC" { str = padCoordinates(str) } rData = append(rData, str) diff --git a/pkg/dns/record_lookup_test.go b/pkg/dns/record_lookup_test.go index ac81c8a3..0b163eb3 100644 --- a/pkg/dns/record_lookup_test.go +++ b/pkg/dns/record_lookup_test.go @@ -7,26 +7,26 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDNS_GetRecord(t *testing.T) { tests := map[string]struct { - zone string - name string - recordType string + params GetRecordRequest responseStatus int responseBody string expectedPath string - expectedResponse *RecordBody + expectedResponse *GetRecordResponse withError error }{ "200 OK": { - zone: "example.com", - name: "www.example.com", - recordType: "A", + params: GetRecordRequest{ + Zone: "example.com", + Name: "www.example.com", + RecordType: "A", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -39,7 +39,7 @@ func TestDNS_GetRecord(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", - expectedResponse: &RecordBody{ + expectedResponse: &GetRecordResponse{ Name: "www.example.com", RecordType: "A", TTL: 300, @@ -48,9 +48,11 @@ func TestDNS_GetRecord(t *testing.T) { }, }, "500 internal server error": { - zone: "example.com", - name: "www.example.com", - recordType: "A", + params: GetRecordRequest{ + Zone: "example.com", + Name: "www.example.com", + RecordType: "A", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -79,7 +81,7 @@ func TestDNS_GetRecord(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetRecord(context.Background(), test.zone, test.name, test.recordType) + result, err := client.GetRecord(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -92,18 +94,18 @@ func TestDNS_GetRecord(t *testing.T) { func TestDNS_GetRecordList(t *testing.T) { tests := map[string]struct { - zone string - name string - recordType string + params GetRecordListRequest responseStatus int responseBody string expectedPath string - expectedResponse *RecordSetResponse + expectedResponse *GetRecordListResponse withError error }{ "200 OK": { - zone: "example.com", - recordType: "A", + params: GetRecordListRequest{ + Zone: "example.com", + RecordType: "A", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -129,7 +131,7 @@ func TestDNS_GetRecordList(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/example.com/recordsets?showAll=true&types=A", - expectedResponse: &RecordSetResponse{ + expectedResponse: &GetRecordListResponse{ Metadata: Metadata{ LastPage: 0, Page: 1, @@ -148,8 +150,10 @@ func TestDNS_GetRecordList(t *testing.T) { }, }, "500 internal server error": { - zone: "example.com", - recordType: "A", + params: GetRecordListRequest{ + Zone: "example.com", + RecordType: "A", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -178,7 +182,7 @@ func TestDNS_GetRecordList(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetRecordList(context.Background(), test.zone, test.name, test.recordType) + result, err := client.GetRecordList(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -191,9 +195,7 @@ func TestDNS_GetRecordList(t *testing.T) { func TestDNS_GetRdata(t *testing.T) { tests := map[string]struct { - zone string - name string - recordType string + params GetRdataRequest responseStatus int responseBody string expectedPath string @@ -201,9 +203,11 @@ func TestDNS_GetRdata(t *testing.T) { withError error }{ "ipv6 test": { - zone: "example.com", - name: "www.example.com", - recordType: "AAAA", + params: GetRdataRequest{ + Zone: "example.com", + RecordType: "AAAA", + Name: "www.example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -231,9 +235,11 @@ func TestDNS_GetRdata(t *testing.T) { expectedResponse: []string{"2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, }, "loc test": { - zone: "example.com", - name: "www.example.com", - recordType: "LOC", + params: GetRdataRequest{ + Zone: "example.com", + RecordType: "LOC", + Name: "www.example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -261,8 +267,11 @@ func TestDNS_GetRdata(t *testing.T) { expectedResponse: []string{"52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000.00m 10.00m"}, }, "500 internal server error": { - zone: "example.com", - recordType: "A", + params: GetRdataRequest{ + Zone: "example.com", + RecordType: "A", + Name: "www.example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -291,7 +300,7 @@ func TestDNS_GetRdata(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetRdata(context.Background(), test.zone, test.name, test.recordType) + result, err := client.GetRdata(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/dns/record_test.go b/pkg/dns/record_test.go index e3d9a4a9..a983a9de 100644 --- a/pkg/dns/record_test.go +++ b/pkg/dns/record_test.go @@ -13,21 +13,24 @@ import ( func TestDNS_CreateRecord(t *testing.T) { tests := map[string]struct { - body RecordBody + params CreateRecordRequest responseStatus int responseBody string expectedPath string withError error }{ "200 OK": { - responseStatus: http.StatusCreated, - body: RecordBody{ - Name: "www.example.com", - RecordType: "A", - TTL: 300, - Target: []string{"10.0.0.2", "10.0.0.3"}, + params: CreateRecordRequest{ + Record: &RecordBody{ + Name: "www.example.com", + RecordType: "A", + TTL: 300, + Target: []string{"10.0.0.2", "10.0.0.3"}, + }, + Zone: "example.com", }, - expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", + responseStatus: http.StatusCreated, + expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", responseBody: ` { "name": "www.example.com", @@ -40,11 +43,14 @@ func TestDNS_CreateRecord(t *testing.T) { }`, }, "500 internal server error": { - body: RecordBody{ - Name: "www.example.com", - RecordType: "A", - TTL: 300, - Target: []string{"10.0.0.2", "10.0.0.3"}, + params: CreateRecordRequest{ + Record: &RecordBody{ + Name: "www.example.com", + RecordType: "A", + TTL: 300, + Target: []string{"10.0.0.2", "10.0.0.3"}, + }, + Zone: "example.com", }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -74,7 +80,7 @@ func TestDNS_CreateRecord(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.CreateRecord(context.Background(), &test.body, "example.com") + err := client.CreateRecord(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -87,21 +93,24 @@ func TestDNS_CreateRecord(t *testing.T) { func TestDNS_UpdateRecord(t *testing.T) { tests := map[string]struct { - body RecordBody + params UpdateRecordRequest responseStatus int responseBody string expectedPath string withError error }{ "204 No Content": { - responseStatus: http.StatusOK, - body: RecordBody{ - Name: "www.example.com", - RecordType: "A", - TTL: 300, - Target: []string{"10.0.0.2", "10.0.0.3"}, + params: UpdateRecordRequest{ + Record: &RecordBody{ + Name: "www.example.com", + RecordType: "A", + TTL: 300, + Target: []string{"10.0.0.2", "10.0.0.3"}, + }, + Zone: "example.com", }, - expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", + responseStatus: http.StatusOK, + expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", responseBody: ` { "name": "www.example.com", @@ -114,11 +123,14 @@ func TestDNS_UpdateRecord(t *testing.T) { }`, }, "500 internal server error": { - body: RecordBody{ - Name: "www.example.com", - RecordType: "A", - TTL: 300, - Target: []string{"10.0.0.2", "10.0.0.3"}, + params: UpdateRecordRequest{ + Record: &RecordBody{ + Name: "www.example.com", + RecordType: "A", + TTL: 300, + Target: []string{"10.0.0.2", "10.0.0.3"}, + }, + Zone: "example.com", }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -148,7 +160,7 @@ func TestDNS_UpdateRecord(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.UpdateRecord(context.Background(), &test.body, "example.com") + err := client.UpdateRecord(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -161,29 +173,27 @@ func TestDNS_UpdateRecord(t *testing.T) { func TestDNS_DeleteRecord(t *testing.T) { tests := map[string]struct { - body RecordBody + params DeleteRecordRequest responseStatus int responseBody string expectedPath string withError error }{ "204 No Content": { - responseStatus: http.StatusNoContent, - body: RecordBody{ + params: DeleteRecordRequest{ Name: "www.example.com", RecordType: "A", - TTL: 300, - Target: []string{"10.0.0.2", "10.0.0.3"}, + Zone: "example.com", }, - expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", - responseBody: ``, + responseStatus: http.StatusNoContent, + expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types/A", + responseBody: ``, }, "500 internal server error": { - body: RecordBody{ + params: DeleteRecordRequest{ Name: "www.example.com", RecordType: "A", - TTL: 300, - Target: []string{"10.0.0.2", "10.0.0.3"}, + Zone: "example.com", }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -213,7 +223,7 @@ func TestDNS_DeleteRecord(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.DeleteRecord(context.Background(), &test.body, "example.com") + err := client.DeleteRecord(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/dns/recordsets.go b/pkg/dns/recordsets.go index 3004e035..7c732366 100644 --- a/pkg/dns/recordsets.go +++ b/pkg/dns/recordsets.go @@ -2,71 +2,109 @@ package dns import ( "context" + "errors" "fmt" "net/http" - - validation "github.com/go-ozzo/ozzo-validation/v4" - "strconv" "sync" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) var ( zoneRecordSetsWriteLock sync.Mutex ) -// Recordsets contains operations available on a record sets. -type Recordsets interface { - // GetRecordSets retrieves record sets with Query Args. No formatting of arg values. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-recordsets - GetRecordSets(context.Context, string, ...RecordSetQueryArgs) (*RecordSetResponse, error) - // CreateRecordSets creates multiple record sets. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-recordsets - CreateRecordSets(context.Context, *RecordSets, string, ...bool) error - // UpdateRecordSets replaces list of record sets. - // - // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-recordsets - UpdateRecordSets(context.Context, *RecordSets, string, ...bool) error -} +type ( + // RecordSetQueryArgs contains query parameters for recordset request + RecordSetQueryArgs struct { + Page int + PageSize int + Search string + ShowAll bool + SortBy string + Types string + } -// RecordSetQueryArgs contains query parameters for recordset request -type RecordSetQueryArgs struct { - Page int - PageSize int - Search string - ShowAll bool - SortBy string - Types string -} + // RecordSets Struct. Used for Create and Update record sets. Contains a list of RecordSet objects + RecordSets struct { + RecordSets []RecordSet `json:"recordsets"` + } -// RecordSets Struct. Used for Create and Update record sets. Contains a list of RecordSet objects -type RecordSets struct { - RecordSets []RecordSet `json:"recordsets"` -} + // RecordSet contains record set metadata + RecordSet struct { + Name string `json:"name"` + Type string `json:"type"` + TTL int `json:"ttl"` + Rdata []string `json:"rdata"` + } + + // Metadata contains metadata of RecordSet response + Metadata struct { + LastPage int `json:"lastPage"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + ShowAll bool `json:"showAll"` + TotalElements int `json:"totalElements"` + } + + // GetRecordSetsRequest contains request parameters for GetRecordSets + GetRecordSetsRequest struct { + Zone string + QueryArgs *RecordSetQueryArgs + } -// RecordSet contains record set metadata -type RecordSet struct { - Name string `json:"name"` - Type string `json:"type"` - TTL int `json:"ttl"` - Rdata []string `json:"rdata"` + // GetRecordSetsResponse contains the response data from GetRecordSets operation + GetRecordSetsResponse struct { + Metadata Metadata `json:"metadata"` + RecordSets []RecordSet `json:"recordsets"` + } + + // RecordSetsRequest contains request parameters + RecordSetsRequest struct { + RecordSets *RecordSets + Zone string + RecLock []bool + } + + // CreateRecordSetsRequest contains request parameters for CreateRecordSets + CreateRecordSetsRequest RecordSetsRequest + + // UpdateRecordSetsRequest contains request parameters for UpdateRecordSets + UpdateRecordSetsRequest RecordSetsRequest +) + +var ( + // ErrCreateRecordSets is returned when CreateRecordSets fails + ErrCreateRecordSets = errors.New("create record sets") + // ErrGetRecordSets is returned when GetRecordSets fails + ErrGetRecordSets = errors.New("get record sets") + // ErrUpdateRecordSets is returned when UpdateRecordSets fails + ErrUpdateRecordSets = errors.New("update record sets") +) + +// Validate validates GetRecordSetsRequest +func (r GetRecordSetsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) } -// Metadata contains metadata of RecordSet response -type Metadata struct { - LastPage int `json:"lastPage"` - Page int `json:"page"` - PageSize int `json:"pageSize"` - ShowAll bool `json:"showAll"` - TotalElements int `json:"totalElements"` +// Validate validates CreateRecordSetsRequest +func (r CreateRecordSetsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "RecordSets": validation.Validate(r.RecordSets, validation.Required), + }) } -// RecordSetResponse contains a response with a list of record sets -type RecordSetResponse struct { - Metadata Metadata `json:"metadata"` - RecordSets []RecordSet `json:"recordsets"` +// Validate validates UpdateRecordSetsRequest +func (r UpdateRecordSetsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "RecordSets": validation.Validate(r.RecordSets, validation.Required), + }) } // Validate validates RecordSets @@ -88,43 +126,43 @@ func (rs *RecordSets) Validate() error { return nil } -func (d *dns) GetRecordSets(ctx context.Context, zone string, queryArgs ...RecordSetQueryArgs) (*RecordSetResponse, error) { +func (d *dns) GetRecordSets(ctx context.Context, params GetRecordSetsRequest) (*GetRecordSetsResponse, error) { logger := d.Log(ctx) logger.Debug("GetRecordSets") - if len(queryArgs) > 1 { - return nil, fmt.Errorf("invalid arguments GetRecordSets QueryArgs") + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetRecordSets, ErrStructValidation, err) } - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", zone) + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetRecordsets request: %w", err) } - q := req.URL.Query() - if len(queryArgs) > 0 { - if queryArgs[0].Page > 0 { - q.Add("page", strconv.Itoa(queryArgs[0].Page)) + if params.QueryArgs != nil { + q := req.URL.Query() + if params.QueryArgs.Page > 0 { + q.Add("page", strconv.Itoa(params.QueryArgs.Page)) } - if queryArgs[0].PageSize > 0 { - q.Add("pageSize", strconv.Itoa(queryArgs[0].PageSize)) + if params.QueryArgs.PageSize > 0 { + q.Add("pageSize", strconv.Itoa(params.QueryArgs.PageSize)) } - if queryArgs[0].Search != "" { - q.Add("search", queryArgs[0].Search) + if params.QueryArgs.Search != "" { + q.Add("search", params.QueryArgs.Search) } - q.Add("showAll", strconv.FormatBool(queryArgs[0].ShowAll)) - if queryArgs[0].SortBy != "" { - q.Add("sortBy", queryArgs[0].SortBy) + q.Add("showAll", strconv.FormatBool(params.QueryArgs.ShowAll)) + if params.QueryArgs.SortBy != "" { + q.Add("sortBy", params.QueryArgs.SortBy) } - if queryArgs[0].Types != "" { - q.Add("types", queryArgs[0].Types) + if params.QueryArgs.Types != "" { + q.Add("types", params.QueryArgs.Types) } req.URL.RawQuery = q.Encode() } - var result RecordSetResponse + var result GetRecordSetsResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetRecordsets request failed: %w", err) @@ -137,14 +175,14 @@ func (d *dns) GetRecordSets(ctx context.Context, zone string, queryArgs ...Recor return &result, nil } -func (d *dns) CreateRecordSets(ctx context.Context, recordSets *RecordSets, zone string, recLock ...bool) error { +func (d *dns) CreateRecordSets(ctx context.Context, params CreateRecordSetsRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone // so we have to save just one request at a time to ensure this is always // incremented properly - if localLock(recLock) { + if localLock(params.RecLock) { zoneRecordSetsWriteLock.Lock() defer zoneRecordSetsWriteLock.Unlock() } @@ -152,16 +190,20 @@ func (d *dns) CreateRecordSets(ctx context.Context, recordSets *RecordSets, zone logger := d.Log(ctx) logger.Debug("CreateRecordSets") - if err := recordSets.Validate(); err != nil { + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrCreateRecordSets, ErrStructValidation, err) + } + + if err := params.RecordSets.Validate(); err != nil { return err } - reqBody, err := convertStructToReqBody(recordSets) + reqBody, err := convertStructToReqBody(params.RecordSets) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - postURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", zone) + postURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody) if err != nil { return fmt.Errorf("failed to create CreateRecordsets request: %w", err) @@ -179,14 +221,14 @@ func (d *dns) CreateRecordSets(ctx context.Context, recordSets *RecordSets, zone return nil } -func (d *dns) UpdateRecordSets(ctx context.Context, recordSets *RecordSets, zone string, recLock ...bool) error { +func (d *dns) UpdateRecordSets(ctx context.Context, params UpdateRecordSetsRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone // so we have to save just one request at a time to ensure this is always // incremented properly - if localLock(recLock) { + if localLock(params.RecLock) { zoneRecordSetsWriteLock.Lock() defer zoneRecordSetsWriteLock.Unlock() } @@ -194,16 +236,20 @@ func (d *dns) UpdateRecordSets(ctx context.Context, recordSets *RecordSets, zone logger := d.Log(ctx) logger.Debug("UpdateRecordsets") - if err := recordSets.Validate(); err != nil { + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrUpdateRecordSets, ErrStructValidation, err) + } + + if err := params.RecordSets.Validate(); err != nil { return err } - reqBody, err := convertStructToReqBody(recordSets) + reqBody, err := convertStructToReqBody(params.RecordSets) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - putURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", zone) + putURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody) if err != nil { return fmt.Errorf("failed to create UpdateRecordsets request: %w", err) diff --git a/pkg/dns/recordsets_test.go b/pkg/dns/recordsets_test.go index 9d082db1..01be675a 100644 --- a/pkg/dns/recordsets_test.go +++ b/pkg/dns/recordsets_test.go @@ -13,17 +13,18 @@ import ( func TestDNS_GetRecordSets(t *testing.T) { tests := map[string]struct { - zone string - args []RecordSetQueryArgs + params GetRecordSetsRequest responseStatus int responseBody string expectedPath string - expectedResponse *RecordSetResponse + expectedResponse *GetRecordSetsResponse withError error }{ "200 OK": { - zone: "example.com", - args: []RecordSetQueryArgs{}, + params: GetRecordSetsRequest{ + Zone: "example.com", + QueryArgs: &RecordSetQueryArgs{}, + }, responseStatus: http.StatusOK, responseBody: ` { @@ -48,8 +49,8 @@ func TestDNS_GetRecordSets(t *testing.T) { } ] }`, - expectedPath: "/config-dns/v2/zones/example.com/recordsets", - expectedResponse: &RecordSetResponse{ + expectedPath: "/config-dns/v2/zones/example.com/recordsets?showAll=false", + expectedResponse: &GetRecordSetsResponse{ Metadata: Metadata{ LastPage: 0, Page: 1, @@ -68,8 +69,10 @@ func TestDNS_GetRecordSets(t *testing.T) { }, }, "500 internal server error": { - zone: "example.com", - args: []RecordSetQueryArgs{}, + params: GetRecordSetsRequest{ + Zone: "example.com", + QueryArgs: &RecordSetQueryArgs{}, + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -78,7 +81,7 @@ func TestDNS_GetRecordSets(t *testing.T) { "detail": "Error fetching authorities", "status": 500 }`, - expectedPath: "/config-dns/v2/zones/example.com/recordsets", + expectedPath: "/config-dns/v2/zones/example.com/recordsets?showAll=false", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -98,7 +101,7 @@ func TestDNS_GetRecordSets(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetRecordSets(context.Background(), test.zone, test.args...) + result, err := client.GetRecordSets(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -111,25 +114,25 @@ func TestDNS_GetRecordSets(t *testing.T) { func TestDNS_CreateRecordSets(t *testing.T) { tests := map[string]struct { - zone string - sets *RecordSets - responseStatus int - responseBody string - expectedPath string - expectedResponse *RecordSetResponse - withError error + params CreateRecordSetsRequest + responseStatus int + responseBody string + expectedPath string + withError error }{ "200 OK": { - zone: "example.com", - sets: &RecordSets{ - []RecordSet{ - { - Name: "www.example.com", - Type: "A", - TTL: 300, - Rdata: []string{ - "10.0.0.2", - "10.0.0.3", + params: CreateRecordSetsRequest{ + Zone: "example.com", + RecordSets: &RecordSets{ + []RecordSet{ + { + Name: "www.example.com", + Type: "A", + TTL: 300, + Rdata: []string{ + "10.0.0.2", + "10.0.0.3", + }, }, }, }, @@ -138,16 +141,18 @@ func TestDNS_CreateRecordSets(t *testing.T) { expectedPath: "/config-dns/v2/zones/example.com/recordsets", }, "500 internal server error": { - zone: "example.com", - sets: &RecordSets{ - []RecordSet{ - { - Name: "www.example.com", - Type: "A", - TTL: 300, - Rdata: []string{ - "10.0.0.2", - "10.0.0.3", + params: CreateRecordSetsRequest{ + Zone: "example.com", + RecordSets: &RecordSets{ + []RecordSet{ + { + Name: "www.example.com", + Type: "A", + TTL: 300, + Rdata: []string{ + "10.0.0.2", + "10.0.0.3", + }, }, }, }, @@ -182,7 +187,7 @@ func TestDNS_CreateRecordSets(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.CreateRecordSets(context.Background(), test.sets, test.zone) + err := client.CreateRecordSets(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -194,25 +199,25 @@ func TestDNS_CreateRecordSets(t *testing.T) { func TestDNS_UpdateRecordSets(t *testing.T) { tests := map[string]struct { - zone string - sets *RecordSets - responseStatus int - responseBody string - expectedPath string - expectedResponse *RecordSetResponse - withError error + params UpdateRecordSetsRequest + responseStatus int + responseBody string + expectedPath string + withError error }{ "200 OK": { - zone: "example.com", - sets: &RecordSets{ - []RecordSet{ - { - Name: "www.example.com", - Type: "A", - TTL: 300, - Rdata: []string{ - "10.0.0.2", - "10.0.0.3", + params: UpdateRecordSetsRequest{ + Zone: "example.com", + RecordSets: &RecordSets{ + []RecordSet{ + { + Name: "www.example.com", + Type: "A", + TTL: 300, + Rdata: []string{ + "10.0.0.2", + "10.0.0.3", + }, }, }, }, @@ -221,16 +226,18 @@ func TestDNS_UpdateRecordSets(t *testing.T) { expectedPath: "/config-dns/v2/zones/example.com/recordsets", }, "500 internal server error": { - zone: "example.com", - sets: &RecordSets{ - []RecordSet{ - { - Name: "www.example.com", - Type: "A", - TTL: 300, - Rdata: []string{ - "10.0.0.2", - "10.0.0.3", + params: UpdateRecordSetsRequest{ + Zone: "example.com", + RecordSets: &RecordSets{ + []RecordSet{ + { + Name: "www.example.com", + Type: "A", + TTL: 300, + Rdata: []string{ + "10.0.0.2", + "10.0.0.3", + }, }, }, }, @@ -265,7 +272,7 @@ func TestDNS_UpdateRecordSets(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.UpdateRecordSets(context.Background(), test.sets, test.zone) + err := client.UpdateRecordSets(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/dns/tsig.go b/pkg/dns/tsig.go index 19525f33..a2097ec8 100644 --- a/pkg/dns/tsig.go +++ b/pkg/dns/tsig.go @@ -2,48 +2,17 @@ package dns import ( "context" + "errors" "fmt" "net/http" - - validation "github.com/go-ozzo/ozzo-validation/v4" - "reflect" "strings" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // TSIGKeys contains operations available on TSIKeyG resource. - TSIGKeys interface { - // ListTSIGKeys lists the TSIG keys used by zones that you are allowed to manage. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-keys - ListTSIGKeys(context.Context, *TSIGQueryString) (*TSIGReportResponse, error) - // GetTSIGKeyZones retrieves DNS Zones using TSIG key. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-keys-used-by - GetTSIGKeyZones(context.Context, *TSIGKey) (*ZoneNameListResponse, error) - // GetTSIGKeyAliases retrieves a DNS Zone's aliases. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-key-used-by - GetTSIGKeyAliases(context.Context, string) (*ZoneNameListResponse, error) - // TSIGKeyBulkUpdate updates Bulk Zones TSIG key. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-keys-bulk-update - TSIGKeyBulkUpdate(context.Context, *TSIGKeyBulkPost) error - // GetTSIGKey retrieves a TSIG key for zone. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-key - GetTSIGKey(context.Context, string) (*TSIGKeyResponse, error) - // DeleteTSIGKey deletes TSIG key for zone. - // - // See: https://techdocs.akamai.com/edge-dns/reference/delete-zones-zone-key - DeleteTSIGKey(context.Context, string) error - // UpdateTSIGKey updates TSIG key for zone. - // - // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-key - UpdateTSIGKey(context.Context, *TSIGKey, string) error - } - // TSIGQueryString contains TSIG query parameters TSIGQueryString struct { ContractIDs []string `json:"contractIds,omitempty"` @@ -58,6 +27,33 @@ type ( Algorithm string `json:"algorithm,omitempty"` Secret string `json:"secret,omitempty"` } + + // TSIGKeyRequest contains request parameter + TSIGKeyRequest struct { + Zone string + } + + // GetTSIGKeyRequest contains request parameters for GetTSIGKey + GetTSIGKeyRequest TSIGKeyRequest + + // GetTSIGKeyResponse contains the response data from GetTSIGKey operation + GetTSIGKeyResponse struct { + TSIGKey + ZoneCount int64 `json:"zonesCount,omitempty"` + } + + // DeleteTSIGKeyRequest contains request parameters for DeleteTSIGKey + DeleteTSIGKeyRequest TSIGKeyRequest + + // GetTSIGKeyAliasesRequest contains request parameters for GetTSIGKeyAliases + GetTSIGKeyAliasesRequest TSIGKeyRequest + + // GetTSIGKeyAliasesResponse contains the response data from GetTSIGKeyAliases operation + GetTSIGKeyAliasesResponse struct { + Zones []string `json:"zones"` + Aliases []string `json:"aliases"` + } + // TSIGKeyResponse contains TSIG key GET response TSIGKeyResponse struct { TSIGKey @@ -86,11 +82,104 @@ type ( // TSIGReportResponse contains response with a list of the TSIG keys used by zones. TSIGReportResponse struct { - Metadata *TSIGReportMeta `json:"metadata"` - Keys []*TSIGKeyResponse `json:"keys,omitempty"` + Metadata *TSIGReportMeta `json:"metadata"` + Keys []TSIGKeyResponse `json:"keys,omitempty"` + } + + // UpdateTSIGKeyRequest contains request parameters for UpdateTSIGKey + UpdateTSIGKeyRequest struct { + TsigKey *TSIGKey + Zone string + } + + // UpdateTSIGKeyBulkRequest contains request parameters for UpdateTSIGKeyBulk + UpdateTSIGKeyBulkRequest struct { + TSIGKeyBulk *TSIGKeyBulkPost + } + + // GetTSIGKeyZonesRequest contains request parameters for GetTSIGKeyZones + GetTSIGKeyZonesRequest struct { + TsigKey *TSIGKey + } + + // GetTSIGKeyZonesResponse contains the response data from GetTSIGKeyZones operation + GetTSIGKeyZonesResponse struct { + Zones []string `json:"zones"` + Aliases []string `json:"aliases"` + } + + // ListTSIGKeysRequest contains request parameters for ListTSIGKeys + ListTSIGKeysRequest struct { + TsigQuery *TSIGQueryString + } + + // ListTSIGKeysResponse contains the response data from ListTSIGKeys operation + ListTSIGKeysResponse struct { + Metadata *TSIGReportMeta `json:"metadata"` + Keys []TSIGKeyResponse `json:"keys,omitempty"` } ) +var ( + // ErrGetTSIGKey is returned when GetTSIGKey fails + ErrGetTSIGKey = errors.New("get tsig key") + // ErrDeleteTSIGKey is returned when DeleteTSIGKey fails + ErrDeleteTSIGKey = errors.New("delete tsig key") + // ErrGetTSIGKeyAliases is returned when GetTSIGKeyAliases fails + ErrGetTSIGKeyAliases = errors.New("get tsig key aliases") + // ErrUpdateTSIGKey is returned when UpdateTSIGKey fails + ErrUpdateTSIGKey = errors.New("updated tsig key") + // ErrUpdateTSIGKeyBulk is returned when UpdateTSIGKeyBulk fails + ErrUpdateTSIGKeyBulk = errors.New("update tsig key for multiple zones") + // ErrGetTSIGKeyZones is returned when GetTSIGKeyZones fails + ErrGetTSIGKeyZones = errors.New("list zones using tsig key") + // ErrListTSIGKeys is returned when ListTSIGKeys fails + ErrListTSIGKeys = errors.New("get a list of the tsig keys") +) + +// Validate validates GetTSIGKeyRequest +func (r GetTSIGKeyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates DeleteTSIGKeyRequest +func (r DeleteTSIGKeyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates GetTSIGKeyAliasesRequest +func (r GetTSIGKeyAliasesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates UpdateTSIGKeyRequest +func (r UpdateTSIGKeyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "TsigKey": validation.Validate(r.TsigKey), + }) +} + +// Validate validates UpdateTSIGKeyBulkRequest +func (r UpdateTSIGKeyBulkRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "TSIGKeyBulk": validation.Validate(r.TSIGKeyBulk, validation.Required), + }) +} + +// Validate validates GetTSIGKeyZonesRequest +func (r GetTSIGKeyZonesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "TsigKey": validation.Validate(r.TsigKey, validation.Required), + }) +} + // Validate validates TSIGKey func (key *TSIGKey) Validate() error { return validation.Errors{ @@ -158,17 +247,17 @@ func constructTSIGQueryString(tsigQueryString *TSIGQueryString) string { return "" } -func (d *dns) ListTSIGKeys(ctx context.Context, tsigQueryString *TSIGQueryString) (*TSIGReportResponse, error) { +func (d *dns) ListTSIGKeys(ctx context.Context, params ListTSIGKeysRequest) (*ListTSIGKeysResponse, error) { logger := d.Log(ctx) logger.Debug("ListTSIGKeys") - getURL := fmt.Sprintf("/config-dns/v2/keys%s", constructTSIGQueryString(tsigQueryString)) + getURL := fmt.Sprintf("/config-dns/v2/keys%s", constructTSIGQueryString(params.TsigQuery)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListTsigKeyss request: %w", err) } - var result TSIGReportResponse + var result ListTSIGKeysResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf(" ListTsigKeys request failed: %w", err) @@ -181,15 +270,15 @@ func (d *dns) ListTSIGKeys(ctx context.Context, tsigQueryString *TSIGQueryString return &result, nil } -func (d *dns) GetTSIGKeyZones(ctx context.Context, tsigKey *TSIGKey) (*ZoneNameListResponse, error) { +func (d *dns) GetTSIGKeyZones(ctx context.Context, params GetTSIGKeyZonesRequest) (*GetTSIGKeyZonesResponse, error) { logger := d.Log(ctx) logger.Debug("GetTSIGKeyZones") - if err := tsigKey.Validate(); err != nil { - return nil, err + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKeyZones, ErrStructValidation, err) } - reqBody, err := convertStructToReqBody(tsigKey) + reqBody, err := convertStructToReqBody(params.TsigKey) if err != nil { return nil, fmt.Errorf("failed to generate request body: %w", err) } @@ -200,7 +289,7 @@ func (d *dns) GetTSIGKeyZones(ctx context.Context, tsigKey *TSIGKey) (*ZoneNameL return nil, fmt.Errorf("failed to create GetTsigKeyZones request: %w", err) } - var result ZoneNameListResponse + var result GetTSIGKeyZonesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetTsigKeyZones request failed: %w", err) @@ -213,17 +302,21 @@ func (d *dns) GetTSIGKeyZones(ctx context.Context, tsigKey *TSIGKey) (*ZoneNameL return &result, nil } -func (d *dns) GetTSIGKeyAliases(ctx context.Context, zone string) (*ZoneNameListResponse, error) { +func (d *dns) GetTSIGKeyAliases(ctx context.Context, params GetTSIGKeyAliasesRequest) (*GetTSIGKeyAliasesResponse, error) { logger := d.Log(ctx) logger.Debug("GetTSIGKeyAliases") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key/used-by", zone) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKeyAliases, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key/used-by", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetTsigKeyAliases request: %w", err) } - var result ZoneNameListResponse + var result GetTSIGKeyAliasesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetTsigKeyAliases request failed: %w", err) @@ -236,15 +329,15 @@ func (d *dns) GetTSIGKeyAliases(ctx context.Context, zone string) (*ZoneNameList return &result, nil } -func (d *dns) TSIGKeyBulkUpdate(ctx context.Context, tsigBulk *TSIGKeyBulkPost) error { +func (d *dns) UpdateTSIGKeyBulk(ctx context.Context, params UpdateTSIGKeyBulkRequest) error { logger := d.Log(ctx) logger.Debug("TSIGKeyBulkUpdate") - if err := tsigBulk.Validate(); err != nil { - return err + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrUpdateTSIGKeyBulk, ErrStructValidation, err) } - reqBody, err := convertStructToReqBody(tsigBulk) + reqBody, err := convertStructToReqBody(params.TSIGKeyBulk) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } @@ -267,17 +360,21 @@ func (d *dns) TSIGKeyBulkUpdate(ctx context.Context, tsigBulk *TSIGKeyBulkPost) return nil } -func (d *dns) GetTSIGKey(ctx context.Context, zone string) (*TSIGKeyResponse, error) { +func (d *dns) GetTSIGKey(ctx context.Context, params GetTSIGKeyRequest) (*GetTSIGKeyResponse, error) { logger := d.Log(ctx) logger.Debug("GetTSIGKey") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", zone) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetTSIGKey, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetTsigKey request: %w", err) } - var result TSIGKeyResponse + var result GetTSIGKeyResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetTsigKey request failed: %w", err) @@ -290,11 +387,15 @@ func (d *dns) GetTSIGKey(ctx context.Context, zone string) (*TSIGKeyResponse, er return &result, nil } -func (d *dns) DeleteTSIGKey(ctx context.Context, zone string) error { +func (d *dns) DeleteTSIGKey(ctx context.Context, params DeleteTSIGKeyRequest) error { logger := d.Log(ctx) logger.Debug("DeleteTSIGKey") - delURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", zone) + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrDeleteTSIGKey, ErrStructValidation, err) + } + + delURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return fmt.Errorf("failed to create DeleteTsigKey request: %w", err) @@ -312,20 +413,20 @@ func (d *dns) DeleteTSIGKey(ctx context.Context, zone string) error { return nil } -func (d *dns) UpdateTSIGKey(ctx context.Context, tsigKey *TSIGKey, zone string) error { +func (d *dns) UpdateTSIGKey(ctx context.Context, params UpdateTSIGKeyRequest) error { logger := d.Log(ctx) logger.Debug("UpdateTSIGKey") - if err := tsigKey.Validate(); err != nil { - return err + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrUpdateTSIGKey, ErrStructValidation, err) } - reqBody, err := convertStructToReqBody(tsigKey) + reqBody, err := convertStructToReqBody(params.TsigKey) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - putURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", zone) + putURL := fmt.Sprintf("/config-dns/v2/zones/%s/key", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody) if err != nil { return fmt.Errorf("failed to create UpdateTsigKey request: %w", err) diff --git a/pkg/dns/tsig_test.go b/pkg/dns/tsig_test.go index b9fa0ef5..e5c491f4 100644 --- a/pkg/dns/tsig_test.go +++ b/pkg/dns/tsig_test.go @@ -13,16 +13,16 @@ import ( func TestDNS_ListTSIGKeys(t *testing.T) { tests := map[string]struct { - query TSIGQueryString + params ListTSIGKeysRequest responseStatus int responseBody string expectedPath string - expectedResponse *TSIGReportResponse + expectedResponse *ListTSIGKeysResponse withError error }{ "200 OK": { - query: TSIGQueryString{ - ContractIDs: []string{"1-1ABCD"}, + params: ListTSIGKeysRequest{ + TsigQuery: &TSIGQueryString{ContractIDs: []string{"1-1ABCD"}}, }, responseStatus: http.StatusOK, responseBody: ` @@ -40,11 +40,11 @@ func TestDNS_ListTSIGKeys(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/keys?contractIds=1-1ABCD", - expectedResponse: &TSIGReportResponse{ + expectedResponse: &ListTSIGKeysResponse{ Metadata: &TSIGReportMeta{ TotalElements: 1, }, - Keys: []*TSIGKeyResponse{ + Keys: []TSIGKeyResponse{ { TSIGKey: TSIGKey{ Name: "a.test.key.", @@ -57,8 +57,8 @@ func TestDNS_ListTSIGKeys(t *testing.T) { }, }, "500 internal server error": { - query: TSIGQueryString{ - ContractIDs: []string{"1-1ABCD"}, + params: ListTSIGKeysRequest{ + TsigQuery: &TSIGQueryString{ContractIDs: []string{"1-1ABCD"}}, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -88,7 +88,7 @@ func TestDNS_ListTSIGKeys(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.ListTSIGKeys(context.Background(), &test.query) + result, err := client.ListTSIGKeys(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -101,18 +101,20 @@ func TestDNS_ListTSIGKeys(t *testing.T) { func TestDNS_GetTSIGKeyZones(t *testing.T) { tests := map[string]struct { - key TSIGKey + params GetTSIGKeyZonesRequest responseStatus int responseBody string expectedPath string - expectedResponse *ZoneNameListResponse + expectedResponse *GetTSIGKeyZonesResponse withError error }{ "200 OK": { - key: TSIGKey{ - Name: "example.com.akamai.com.", - Algorithm: "hmac-sha512", - Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + params: GetTSIGKeyZonesRequest{ + TsigKey: &TSIGKey{ + Name: "example.com.akamai.com.", + Algorithm: "hmac-sha512", + Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + }, }, responseStatus: http.StatusOK, responseBody: ` @@ -123,15 +125,17 @@ func TestDNS_GetTSIGKeyZones(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/keys/used-by", - expectedResponse: &ZoneNameListResponse{ + expectedResponse: &GetTSIGKeyZonesResponse{ Zones: []string{"river.com", "stream.com"}, }, }, "500 internal server error": { - key: TSIGKey{ - Name: "example.com.akamai.com.", - Algorithm: "hmac-sha512", - Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + params: GetTSIGKeyZonesRequest{ + TsigKey: &TSIGKey{ + Name: "example.com.akamai.com.", + Algorithm: "hmac-sha512", + Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + }, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -161,7 +165,7 @@ func TestDNS_GetTSIGKeyZones(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetTSIGKeyZones(context.Background(), &test.key) + result, err := client.GetTSIGKeyZones(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -174,15 +178,17 @@ func TestDNS_GetTSIGKeyZones(t *testing.T) { func TestDNS_GetTSIGKeyAliases(t *testing.T) { tests := map[string]struct { - zone string + params GetTSIGKeyAliasesRequest responseStatus int responseBody string expectedPath string - expectedResponse *ZoneNameListResponse + expectedResponse *GetTSIGKeyAliasesResponse withError error }{ "200 OK": { - zone: "example.com", + params: GetTSIGKeyAliasesRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -194,12 +200,14 @@ func TestDNS_GetTSIGKeyAliases(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/example.com/key/used-by", - expectedResponse: &ZoneNameListResponse{ + expectedResponse: &GetTSIGKeyAliasesResponse{ Aliases: []string{"exmaple.com", "river.com", "brook.com", "ocean.com"}, }, }, "500 internal server error": { - zone: "example.com", + params: GetTSIGKeyAliasesRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -228,7 +236,7 @@ func TestDNS_GetTSIGKeyAliases(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetTSIGKeyAliases(context.Background(), test.zone) + result, err := client.GetTSIGKeyAliases(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -241,7 +249,7 @@ func TestDNS_GetTSIGKeyAliases(t *testing.T) { func TestDNS_TSIGKeyBulkUpdate(t *testing.T) { tests := map[string]struct { - bulk TSIGKeyBulkPost + params UpdateTSIGKeyBulkRequest responseStatus int responseBody string expectedPath string @@ -249,13 +257,15 @@ func TestDNS_TSIGKeyBulkUpdate(t *testing.T) { withError error }{ "200 OK": { - bulk: TSIGKeyBulkPost{ - Key: &TSIGKey{ - Name: "brook.com.akamai.com.", - Algorithm: "hmac-sha512", - Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + params: UpdateTSIGKeyBulkRequest{ + TSIGKeyBulk: &TSIGKeyBulkPost{ + Key: &TSIGKey{ + Name: "brook.com.akamai.com.", + Algorithm: "hmac-sha512", + Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + }, + Zones: []string{"brook.com", "river.com"}, }, - Zones: []string{"brook.com", "river.com"}, }, responseStatus: http.StatusNoContent, expectedPath: "/config-dns/v2/keys/bulk-update", @@ -264,13 +274,15 @@ func TestDNS_TSIGKeyBulkUpdate(t *testing.T) { }, }, "500 internal server error": { - bulk: TSIGKeyBulkPost{ - Key: &TSIGKey{ - Name: "brook.com.akamai.com.", - Algorithm: "hmac-sha512", - Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + params: UpdateTSIGKeyBulkRequest{ + TSIGKeyBulk: &TSIGKeyBulkPost{ + Key: &TSIGKey{ + Name: "brook.com.akamai.com.", + Algorithm: "hmac-sha512", + Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + }, + Zones: []string{"brook.com", "river.com"}, }, - Zones: []string{"brook.com", "river.com"}, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -302,7 +314,7 @@ func TestDNS_TSIGKeyBulkUpdate(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.TSIGKeyBulkUpdate(context.Background(), &test.bulk) + err := client.UpdateTSIGKeyBulk(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -314,15 +326,17 @@ func TestDNS_TSIGKeyBulkUpdate(t *testing.T) { func TestDNS_GetTSIGKey(t *testing.T) { tests := map[string]struct { - zone string + params GetTSIGKeyRequest responseStatus int responseBody string expectedPath string - expectedResponse *TSIGKeyResponse + expectedResponse *GetTSIGKeyResponse withError error }{ "200 OK": { - zone: "example.com", + params: GetTSIGKeyRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -332,7 +346,7 @@ func TestDNS_GetTSIGKey(t *testing.T) { "zonesCount": 7 }`, expectedPath: "/config-dns/v2/zones/example.com/key", - expectedResponse: &TSIGKeyResponse{ + expectedResponse: &GetTSIGKeyResponse{ TSIGKey: TSIGKey{ Name: "example.com.akamai.com.", Algorithm: "hmac-sha512", @@ -342,7 +356,9 @@ func TestDNS_GetTSIGKey(t *testing.T) { }, }, "500 internal server error": { - zone: "example.com", + params: GetTSIGKeyRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -371,7 +387,7 @@ func TestDNS_GetTSIGKey(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetTSIGKey(context.Background(), test.zone) + result, err := client.GetTSIGKey(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -384,7 +400,7 @@ func TestDNS_GetTSIGKey(t *testing.T) { func TestDNS_DeleteTSIGKey(t *testing.T) { tests := map[string]struct { - zone string + params DeleteTSIGKeyRequest responseStatus int responseBody string expectedPath string @@ -392,12 +408,16 @@ func TestDNS_DeleteTSIGKey(t *testing.T) { withError error }{ "204 No Content": { - zone: "example.com", + params: DeleteTSIGKeyRequest{ + Zone: "example.com", + }, responseStatus: http.StatusNoContent, expectedPath: "/config-dns/v2/zones/example.com/key", }, "500 internal server error": { - zone: "example.com", + params: DeleteTSIGKeyRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -428,7 +448,7 @@ func TestDNS_DeleteTSIGKey(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.DeleteTSIGKey(context.Background(), test.zone) + err := client.DeleteTSIGKey(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -440,8 +460,7 @@ func TestDNS_DeleteTSIGKey(t *testing.T) { func TestDNS_UpdateTSIGKey(t *testing.T) { tests := map[string]struct { - key TSIGKey - zone string + params UpdateTSIGKeyRequest responseStatus int responseBody string expectedPath string @@ -449,22 +468,26 @@ func TestDNS_UpdateTSIGKey(t *testing.T) { withError error }{ "200 OK": { - key: TSIGKey{ - Name: "example.com.akamai.com.", - Algorithm: "hmac-sha512", - Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + params: UpdateTSIGKeyRequest{ + TsigKey: &TSIGKey{ + Name: "example.com.akamai.com.", + Algorithm: "hmac-sha512", + Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + }, + Zone: "example.com", }, - zone: "example.com", responseStatus: http.StatusNoContent, expectedPath: "/config-dns/v2/zones/example.com/key", }, "500 internal server error": { - key: TSIGKey{ - Name: "example.com.akamai.com.", - Algorithm: "hmac-sha512", - Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + params: UpdateTSIGKeyRequest{ + TsigKey: &TSIGKey{ + Name: "example.com.akamai.com.", + Algorithm: "hmac-sha512", + Secret: "Ok1qR5IW1ajVka5cHPEJQIXfLyx5V3PSkFBROAzOn21JumDq6nIpoj6H8rfj5Uo+Ok55ZWQ0Wgrf302fDscHLw==", + }, + Zone: "example.com", }, - zone: "example.com", responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -495,7 +518,7 @@ func TestDNS_UpdateTSIGKey(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.UpdateTSIGKey(context.Background(), &test.key, test.zone) + err := client.UpdateTSIGKey(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/dns/zone.go b/pkg/dns/zone.go index 79ab0538..9e0311cd 100644 --- a/pkg/dns/zone.go +++ b/pkg/dns/zone.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -14,7 +15,7 @@ import ( "sync" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) @@ -23,82 +24,6 @@ var ( ) type ( - // Zones contains operations available on Zone resources. - Zones interface { - // ListZones retrieves a list of all zones user can access. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones - ListZones(context.Context, ...ZoneListQueryArgs) (*ZoneListResponse, error) - // GetZone retrieves Zone metadata. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zone - GetZone(context.Context, string) (*ZoneResponse, error) - //GetChangeList retrieves Zone changelist. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-changelists-zone - GetChangeList(context.Context, string) (*ChangeListResponse, error) - // GetMasterZoneFile retrieves master zone file. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-zone-file - GetMasterZoneFile(context.Context, string) (string, error) - // PostMasterZoneFile updates master zone file. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-zone-file - PostMasterZoneFile(context.Context, string, string) error - // CreateZone creates new zone. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zone - CreateZone(context.Context, *ZoneCreate, ZoneQueryString, ...bool) error - // SaveChangelist creates a new Change List based on the most recent version of a zone. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-changelists - SaveChangelist(context.Context, *ZoneCreate) error - // SubmitChangelist submits changelist for the Zone to create default NS SOA records. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-changelists-zone-submit - SubmitChangelist(context.Context, *ZoneCreate) error - // UpdateZone updates zone. - // - // See: https://techdocs.akamai.com/edge-dns/reference/put-zone - UpdateZone(context.Context, *ZoneCreate, ZoneQueryString) error - // GetZoneNames retrieves a list of a zone's record names. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-names - GetZoneNames(context.Context, string) (*ZoneNamesResponse, error) - // GetZoneNameTypes retrieves a zone name's record types. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-name-types - GetZoneNameTypes(context.Context, string, string) (*ZoneNameTypesResponse, error) - // CreateBulkZones submits create bulk zone request. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-create-requests - CreateBulkZones(context.Context, *BulkZonesCreate, ZoneQueryString) (*BulkZonesResponse, error) - // DeleteBulkZones submits delete bulk zone request. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-delete-requests - DeleteBulkZones(context.Context, *ZoneNameListResponse, ...bool) (*BulkZonesResponse, error) - // GetBulkZoneCreateStatus retrieves submit request status. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-create-requests-requestid - GetBulkZoneCreateStatus(context.Context, string) (*BulkStatusResponse, error) - //GetBulkZoneDeleteStatus retrieves submit request status. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-delete-requests-requestid - GetBulkZoneDeleteStatus(context.Context, string) (*BulkStatusResponse, error) - // GetBulkZoneCreateResult retrieves create request result. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-create-requests-requestid-result - GetBulkZoneCreateResult(ctx context.Context, requestid string) (*BulkCreateResultResponse, error) - // GetBulkZoneDeleteResult retrieves delete request result. - // - // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-delete-requests-requestid-result - GetBulkZoneDeleteResult(context.Context, string) (*BulkDeleteResultResponse, error) - // GetZonesDNSSecStatus returns the current DNSSEC status for one or more zones. - // - // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-dns-sec-status - GetZonesDNSSecStatus(context.Context, GetZonesDNSSecStatusRequest) (*GetZonesDNSSecStatusResponse, error) - } - // ZoneQueryString contains zone query parameters ZoneQueryString struct { Contract string @@ -139,17 +64,6 @@ type ( VersionID string `json:"versionId,omitempty"` } - // ZoneListQueryArgs contains parameters for List Zones query - ZoneListQueryArgs struct { - ContractIDs string - Page int - PageSize int - Search string - ShowAll bool - SortBy string - Types string - } - // ListMetadata contains metadata for List Zones request ListMetadata struct { ContractIDs []string `json:"contractIds"` @@ -161,12 +75,20 @@ type ( // ZoneListResponse contains response for List Zones request ZoneListResponse struct { - Metadata *ListMetadata `json:"metadata,omitempty"` - Zones []*ZoneResponse `json:"zones,omitempty"` + Metadata *ListMetadata `json:"metadata,omitempty"` + Zones []ZoneResponse `json:"zones,omitempty"` } - // ChangeListResponse contains metadata about a change list - ChangeListResponse struct { + // ZoneRequest contains request parameters + ZoneRequest struct { + Zone string + } + + // GetZoneResponse contains the response data from GetZone operation + GetZoneResponse ZoneResponse + + // GetChangeListResponse contains metadata about a change list + GetChangeListResponse struct { Zone string `json:"zone,omitempty"` ChangeTag string `json:"changeTag,omitempty"` ZoneVersionID string `json:"zoneVersionId,omitempty"` @@ -180,15 +102,63 @@ type ( Aliases []string `json:"aliases,omitempty"` } - // ZoneNamesResponse contains record set names for zone - ZoneNamesResponse struct { + // GetZoneNamesResponse contains record set names for zone + GetZoneNamesResponse struct { Names []string `json:"names"` } - // ZoneNameTypesResponse contains record set types for zone - ZoneNameTypesResponse struct { + // GetZoneNameTypesResponse contains record set types for zone + GetZoneNameTypesResponse struct { Types []string `json:"types"` } + // GetZoneRequest contains request parameters for GetZone + GetZoneRequest ZoneRequest + + // GetChangeListRequest contains request parameters for GetChangeList + GetChangeListRequest ZoneRequest + + // ListZonesRequest contains request parameters for ListZones + ListZonesRequest struct { + ContractIDs string + Page int + PageSize int + Search string + ShowAll bool + SortBy string + Types string + } + // GetMasterZoneFileRequest contains request parameters for GetMasterZoneFile + GetMasterZoneFileRequest ZoneRequest + + // PostMasterZoneFileRequest contains request parameters for PostMasterZoneFile + PostMasterZoneFileRequest struct { + Zone string + FileData string + } + // CreateZoneRequest contains request parameters for CreateZone + CreateZoneRequest struct { + CreateZone *ZoneCreate + ZoneQueryString ZoneQueryString + ClearConn []bool + } + // SaveChangeListRequest contains request parameters for SaveChangelist + SaveChangeListRequest ZoneCreate + + // SubmitChangeListRequest contains request parameters for SubmitChangeList + SubmitChangeListRequest ZoneCreate + + // UpdateZoneRequest contains request parameters for UpdateZone + UpdateZoneRequest struct { + CreateZone *ZoneCreate + } + // GetZoneNamesRequest contains request parameters for GetZoneNames + GetZoneNamesRequest ZoneRequest + + // GetZoneNameTypesRequest contains request parameters for GetZoneNameTypes + GetZoneNameTypesRequest struct { + Zone string + ZoneName string + } // GetZonesDNSSecStatusRequest is used to get the DNSSEC status for one or more zones GetZonesDNSSecStatusRequest struct { @@ -218,6 +188,91 @@ type ( } ) +var ( + // ErrGetZone is returned when GetZone fails + ErrGetZone = errors.New("get zone") + // ErrGetChangeList is returned when GetChangeList fails + ErrGetChangeList = errors.New("get change list") + // ErrGetMasterZoneFile is returned when GetMasterZoneFile fails + ErrGetMasterZoneFile = errors.New("get master zone file") + // ErrPostMasterZoneFile is returned when PostMasterZoneFile fails + ErrPostMasterZoneFile = errors.New("post master zone file") + // ErrCreateZone is returned when CreateZone fails + ErrCreateZone = errors.New("create zone") + // ErrSaveChangeList is returned when SaveChangeList fails + ErrSaveChangeList = errors.New("save change list") + // ErrSubmitChangeList is returned when SubmitChangeList fails + ErrSubmitChangeList = errors.New("submit change list") + // ErrGetZoneNames is returned when GetZoneNames fails + ErrGetZoneNames = errors.New("get zone names") + // ErrGetZoneNameTypes is returned when GetZoneNameTypes fails + ErrGetZoneNameTypes = errors.New("get zone name types") +) + +// Validate validates GetZoneNameTypesRequest +func (r GetZoneNameTypesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + "ZoneName": validation.Validate(r.ZoneName, validation.Required), + }) +} + +// Validate validates GetZoneNamesRequest +func (r GetZoneNamesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates SubmitChangeListRequest +func (r SubmitChangeListRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates SaveChangelistRequest +func (r SaveChangeListRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates PostMasterZoneFileRequest +func (r PostMasterZoneFileRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates CreateZoneRequest +func (r CreateZoneRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "ZoneQueryString": validation.Validate(r.ZoneQueryString, validation.Required), + }) +} + +// Validate validates GetZoneRequest +func (r GetZoneRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates GetMasterZoneFileRequest +func (r GetMasterZoneFileRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + +// Validate validates GetChangeListRequest +func (r GetChangeListRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Zone": validation.Validate(r.Zone, validation.Required), + }) +} + // Validate validates GetZonesDNSSecStatusRequest func (r GetZonesDNSSecStatusRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ @@ -246,14 +301,11 @@ func convertStructToReqBody(srcStruct interface{}) (io.Reader, error) { return bytes.NewBuffer(reqBody), nil } -func (d *dns) ListZones(ctx context.Context, queryArgs ...ZoneListQueryArgs) (*ZoneListResponse, error) { +func (d *dns) ListZones(ctx context.Context, params ListZonesRequest) (*ZoneListResponse, error) { logger := d.Log(ctx) logger.Debug("ListZones") getURL := fmt.Sprintf("/config-dns/v2/zones") - if len(queryArgs) > 1 { - return nil, fmt.Errorf("ListZones QueryArgs invalid") - } req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { @@ -261,28 +313,26 @@ func (d *dns) ListZones(ctx context.Context, queryArgs ...ZoneListQueryArgs) (*Z } q := req.URL.Query() - if len(queryArgs) > 0 { - if queryArgs[0].Page > 0 { - q.Add("page", strconv.Itoa(queryArgs[0].Page)) - } - if queryArgs[0].PageSize > 0 { - q.Add("pageSize", strconv.Itoa(queryArgs[0].PageSize)) - } - if queryArgs[0].Search != "" { - q.Add("search", queryArgs[0].Search) - } - q.Add("showAll", strconv.FormatBool(queryArgs[0].ShowAll)) - if queryArgs[0].SortBy != "" { - q.Add("sortBy", queryArgs[0].SortBy) - } - if queryArgs[0].Types != "" { - q.Add("types", queryArgs[0].Types) - } - if queryArgs[0].ContractIDs != "" { - q.Add("contractIds", queryArgs[0].ContractIDs) - } - req.URL.RawQuery = q.Encode() + if params.Page > 0 { + q.Add("page", strconv.Itoa(params.Page)) + } + if params.PageSize > 0 { + q.Add("pageSize", strconv.Itoa(params.PageSize)) + } + if params.Search != "" { + q.Add("search", params.Search) + } + q.Add("showAll", strconv.FormatBool(params.ShowAll)) + if params.SortBy != "" { + q.Add("sortBy", params.SortBy) } + if params.Types != "" { + q.Add("types", params.Types) + } + if params.ContractIDs != "" { + q.Add("contractIds", params.ContractIDs) + } + req.URL.RawQuery = q.Encode() var result ZoneListResponse resp, err := d.Exec(req, &result) @@ -297,17 +347,21 @@ func (d *dns) ListZones(ctx context.Context, queryArgs ...ZoneListQueryArgs) (*Z return &result, nil } -func (d *dns) GetZone(ctx context.Context, zoneName string) (*ZoneResponse, error) { +func (d *dns) GetZone(ctx context.Context, params GetZoneRequest) (*GetZoneResponse, error) { logger := d.Log(ctx) logger.Debug("GetZone") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s", zoneName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetZone, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetZone request: %w", err) } - var result ZoneResponse + var result GetZoneResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetZone request failed: %w", err) @@ -320,17 +374,21 @@ func (d *dns) GetZone(ctx context.Context, zoneName string) (*ZoneResponse, erro return &result, nil } -func (d *dns) GetChangeList(ctx context.Context, zone string) (*ChangeListResponse, error) { +func (d *dns) GetChangeList(ctx context.Context, params GetChangeListRequest) (*GetChangeListResponse, error) { logger := d.Log(ctx) logger.Debug("GetChangeList") - getURL := fmt.Sprintf("/config-dns/v2/changelists/%s", zone) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetChangeList, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/changelists/%s", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetChangeList request: %w", err) } - var result ChangeListResponse + var result GetChangeListResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetChangeList request failed: %w", err) @@ -343,11 +401,15 @@ func (d *dns) GetChangeList(ctx context.Context, zone string) (*ChangeListRespon return &result, nil } -func (d *dns) GetMasterZoneFile(ctx context.Context, zone string) (string, error) { +func (d *dns) GetMasterZoneFile(ctx context.Context, params GetMasterZoneFileRequest) (string, error) { logger := d.Log(ctx) logger.Debug("GetMasterZoneFile") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", zone) + if err := params.Validate(); err != nil { + return "", fmt.Errorf("%s: %w: %s", ErrGetMasterZoneFile, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return "", fmt.Errorf("failed to create GetMasterZoneFile request: %w", err) @@ -371,13 +433,17 @@ func (d *dns) GetMasterZoneFile(ctx context.Context, zone string) (string, error return string(masterFile), nil } -func (d *dns) PostMasterZoneFile(ctx context.Context, zone string, fileData string) error { +func (d *dns) PostMasterZoneFile(ctx context.Context, params PostMasterZoneFileRequest) error { logger := d.Log(ctx) logger.Debug("PostMasterZoneFile") + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrPostMasterZoneFile, ErrStructValidation, err) + } + mtResp := "" - pmzfURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", zone) - buf := bytes.NewReader([]byte(fileData)) + pmzfURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", params.Zone) + buf := bytes.NewReader([]byte(params.FileData)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, pmzfURL, buf) if err != nil { return fmt.Errorf("failed to create PostMasterZoneFile request: %w", err) @@ -397,7 +463,7 @@ func (d *dns) PostMasterZoneFile(ctx context.Context, zone string, fileData stri return nil } -func (d *dns) CreateZone(ctx context.Context, zone *ZoneCreate, zoneQueryString ZoneQueryString, clearConn ...bool) error { +func (d *dns) CreateZone(ctx context.Context, params CreateZoneRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone, @@ -410,16 +476,20 @@ func (d *dns) CreateZone(ctx context.Context, zone *ZoneCreate, zoneQueryString logger := d.Log(ctx) logger.Debug("Zone Create") - if err := ValidateZone(zone); err != nil { + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrCreateZone, ErrStructValidation, err) + } + + if err := ValidateZone(params.CreateZone); err != nil { return err } - zoneMap := filterZoneCreate(zone) + zoneMap := filterZoneCreate(params.CreateZone) var zoneResponse ZoneResponse - zoneURL := "/config-dns/v2/zones/?contractId=" + zoneQueryString.Contract - if len(zoneQueryString.Group) > 0 { - zoneURL += "&gid=" + zoneQueryString.Group + zoneURL := "/config-dns/v2/zones/?contractId=" + params.ZoneQueryString.Contract + if len(params.ZoneQueryString.Group) > 0 { + zoneURL += "&gid=" + params.ZoneQueryString.Group } reqBody, err := convertStructToReqBody(zoneMap) @@ -441,9 +511,9 @@ func (d *dns) CreateZone(ctx context.Context, zone *ZoneCreate, zoneQueryString return d.Error(resp) } - if strings.ToUpper(zone.Type) == "PRIMARY" { + if strings.ToUpper(params.CreateZone.Type) == "PRIMARY" { // Timing issue with Create immediately followed by SaveChangelist - for _, clear := range clearConn { + for _, clear := range params.ClearConn { // should only be one entry if clear { logger.Info("Clearing Idle Connections") @@ -455,7 +525,7 @@ func (d *dns) CreateZone(ctx context.Context, zone *ZoneCreate, zoneQueryString return nil } -func (d *dns) SaveChangelist(ctx context.Context, zone *ZoneCreate) error { +func (d *dns) SaveChangeList(ctx context.Context, params SaveChangeListRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone @@ -468,12 +538,16 @@ func (d *dns) SaveChangelist(ctx context.Context, zone *ZoneCreate) error { logger := d.Log(ctx) logger.Debug("SaveChangeList") + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrSaveChangeList, ErrStructValidation, err) + } + reqBody, err := convertStructToReqBody("") if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - postURL := fmt.Sprintf("/config-dns/v2/changelists/?zone=%s", zone.Zone) + postURL := fmt.Sprintf("/config-dns/v2/changelists/?zone=%s", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody) if err != nil { return fmt.Errorf("failed to create SaveChangeList request: %w", err) @@ -491,7 +565,7 @@ func (d *dns) SaveChangelist(ctx context.Context, zone *ZoneCreate) error { return nil } -func (d *dns) SubmitChangelist(ctx context.Context, zone *ZoneCreate) error { +func (d *dns) SubmitChangeList(ctx context.Context, params SubmitChangeListRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone @@ -504,12 +578,16 @@ func (d *dns) SubmitChangelist(ctx context.Context, zone *ZoneCreate) error { logger := d.Log(ctx) logger.Debug("SubmitChangeList") + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrSubmitChangeList, ErrStructValidation, err) + } + reqBody, err := convertStructToReqBody("") if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - postURL := fmt.Sprintf("/config-dns/v2/changelists/%s/submit", zone.Zone) + postURL := fmt.Sprintf("/config-dns/v2/changelists/%s/submit", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody) if err != nil { return fmt.Errorf("failed to create SubmitChangeList request: %w", err) @@ -527,7 +605,7 @@ func (d *dns) SubmitChangelist(ctx context.Context, zone *ZoneCreate) error { return nil } -func (d *dns) UpdateZone(ctx context.Context, zone *ZoneCreate, _ ZoneQueryString) error { +func (d *dns) UpdateZone(ctx context.Context, params UpdateZoneRequest) error { // This lock will restrict the concurrency of API calls // to 1 save request at a time. This is needed for the Soa.Serial value which // is required to be incremented for every subsequent update to a zone @@ -540,17 +618,17 @@ func (d *dns) UpdateZone(ctx context.Context, zone *ZoneCreate, _ ZoneQueryStrin logger := d.Log(ctx) logger.Debug("Zone Update") - if err := ValidateZone(zone); err != nil { + if err := ValidateZone(params.CreateZone); err != nil { return err } - zoneMap := filterZoneCreate(zone) + zoneMap := filterZoneCreate(params.CreateZone) reqBody, err := convertStructToReqBody(zoneMap) if err != nil { return fmt.Errorf("failed to generate request body: %w", err) } - putURL := fmt.Sprintf("/config-dns/v2/zones/%s", zone.Zone) + putURL := fmt.Sprintf("/config-dns/v2/zones/%s", params.CreateZone.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody) if err != nil { return fmt.Errorf("failed to create Get Update request: %w", err) @@ -644,17 +722,21 @@ func ValidateZone(zone *ZoneCreate) error { return nil } -func (d *dns) GetZoneNames(ctx context.Context, zone string) (*ZoneNamesResponse, error) { +func (d *dns) GetZoneNames(ctx context.Context, params GetZoneNamesRequest) (*GetZoneNamesResponse, error) { logger := d.Log(ctx) logger.Debug("GetZoneNames") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names", zone) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetZoneNames, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names", params.Zone) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetZoneNames request: %w", err) } - var result ZoneNamesResponse + var result GetZoneNamesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetZoneNames request failed: %w", err) @@ -667,17 +749,21 @@ func (d *dns) GetZoneNames(ctx context.Context, zone string) (*ZoneNamesResponse return &result, nil } -func (d *dns) GetZoneNameTypes(ctx context.Context, zName, zone string) (*ZoneNameTypesResponse, error) { +func (d *dns) GetZoneNameTypes(ctx context.Context, params GetZoneNameTypesRequest) (*GetZoneNameTypesResponse, error) { logger := d.Log(ctx) logger.Debug(" GetZoneNameTypes") - getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types", zone, zName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetZoneNameTypes, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types", params.Zone, params.ZoneName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetZoneNameTypes request: %w", err) } - var result ZoneNameTypesResponse + var result GetZoneNameTypesResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetZoneNameTypes request failed: %w", err) diff --git a/pkg/dns/zone_test.go b/pkg/dns/zone_test.go index 8a66ca9f..b08ff4cd 100644 --- a/pkg/dns/zone_test.go +++ b/pkg/dns/zone_test.go @@ -8,8 +8,8 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/internal/test" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -17,7 +17,7 @@ import ( func TestDNS_ListZones(t *testing.T) { tests := map[string]struct { - args []ZoneListQueryArgs + params ListZonesRequest responseStatus int responseBody string expectedPath string @@ -26,15 +26,13 @@ func TestDNS_ListZones(t *testing.T) { headers http.Header }{ "200 OK": { - args: []ZoneListQueryArgs{ - { - ContractIDs: "1-1ACYUM", - Search: "org", - SortBy: "-contractId,zone", - Types: "primary,alias", - Page: 1, - PageSize: 25, - }, + params: ListZonesRequest{ + ContractIDs: "1-1ACYUM", + Search: "org", + SortBy: "-contractId,zone", + Types: "primary,alias", + Page: 1, + PageSize: 25, }, headers: http.Header{ "Accept": []string{"application/json"}, @@ -75,7 +73,7 @@ func TestDNS_ListZones(t *testing.T) { TotalElements: 17, ContractIDs: []string{"1-2ABCDE"}, }, - Zones: []*ZoneResponse{ + Zones: []ZoneResponse{ { ContractID: "1-2ABCDE", Zone: "example.com", @@ -92,15 +90,13 @@ func TestDNS_ListZones(t *testing.T) { }, }, "500 internal server error": { - args: []ZoneListQueryArgs{ - { - ContractIDs: "1-1ACYUM", - Search: "org", - SortBy: "-contractId,zone", - Types: "primary,alias", - Page: 1, - PageSize: 25, - }, + params: ListZonesRequest{ + ContractIDs: "1-1ACYUM", + Search: "org", + SortBy: "-contractId,zone", + Types: "primary,alias", + Page: 1, + PageSize: 25, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -132,7 +128,7 @@ func TestDNS_ListZones(t *testing.T) { result, err := client.ListZones( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.args...) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -303,15 +299,17 @@ func TestDNS_GetZonesDNSSecStatus(t *testing.T) { func TestDNS_GetZone(t *testing.T) { tests := map[string]struct { - zone string + params GetZoneRequest responseStatus int responseBody string expectedPath string - expectedResponse *ZoneResponse + expectedResponse *GetZoneResponse withError error }{ "200 OK": { - zone: "example.com", + params: GetZoneRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -328,7 +326,7 @@ func TestDNS_GetZone(t *testing.T) { "activationState": "ACTIVE" }`, expectedPath: "/config-dns/v2/zones/example.com", - expectedResponse: &ZoneResponse{ + expectedResponse: &GetZoneResponse{ ContractID: "1-2ABCDE", Zone: "example.com", Type: "primary", @@ -343,7 +341,9 @@ func TestDNS_GetZone(t *testing.T) { }, }, "500 internal server error": { - zone: "example.com", + params: GetZoneRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -372,7 +372,7 @@ func TestDNS_GetZone(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetZone(context.Background(), test.zone) + result, err := client.GetZone(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -385,7 +385,7 @@ func TestDNS_GetZone(t *testing.T) { func TestDNS_GetZoneMasterFile(t *testing.T) { tests := map[string]struct { - zone string + params GetMasterZoneFileRequest responseStatus int responseBody string expectedPath string @@ -393,7 +393,9 @@ func TestDNS_GetZoneMasterFile(t *testing.T) { withError error }{ "200 OK": { - zone: "example.com", + params: GetMasterZoneFileRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: `"example.com. 10000 IN SOA ns1.akamaidns.com. webmaster.example.com. 1 28800 14400 2419200 86400 example.com. 10000 IN NS ns1.akamaidns.com. @@ -412,7 +414,9 @@ www.example.com. 300 IN A 10.0.0.1 www.example.com. 300 IN A 10.0.0.2"`, }, "500 internal server error": { - zone: "example.com", + params: GetMasterZoneFileRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -441,7 +445,7 @@ www.example.com. 300 IN A 10.0.0.2"`, assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetMasterZoneFile(context.Background(), test.zone) + result, err := client.GetMasterZoneFile(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -454,35 +458,38 @@ www.example.com. 300 IN A 10.0.0.2"`, func TestDNS_UpdateZoneMasterFile(t *testing.T) { tests := map[string]struct { - zone string - masterfile string + params PostMasterZoneFileRequest responseStatus int expectedPath string responseBody string withError error }{ "204 Updated": { - zone: "example.com", - masterfile: `"example.com. 10000 IN SOA ns1.akamaidns.com. webmaster.example.com. 1 28800 14400 2419200 86400 + params: PostMasterZoneFileRequest{ + Zone: "example.com", + FileData: `"example.com. 10000 IN SOA ns1.akamaidns.com. webmaster.example.com. 1 28800 14400 2419200 86400 example.com. 10000 IN NS ns1.akamaidns.com. example.com. 10000 IN NS ns2.akamaidns.com. example.com. 300 IN A 10.0.0.1 example.com. 300 IN A 10.0.0.2 www.example.com. 300 IN A 10.0.0.1 www.example.com. 300 IN A 10.0.0.2"`, + }, responseStatus: http.StatusNoContent, responseBody: "", expectedPath: "/config-dns/v2/zones/example.com/zone-file", }, "500 internal server error": { - zone: "example.com", - masterfile: `"example.com. 10000 IN SOA ns1.akamaidns.com. webmaster.example.com. 1 28800 14400 2419200 86400 + params: PostMasterZoneFileRequest{ + Zone: "example.com", + FileData: `"example.com. 10000 IN SOA ns1.akamaidns.com. webmaster.example.com. 1 28800 14400 2419200 86400 example.com. 10000 IN NS ns1.akamaidns.com. example.com. 10000 IN NS ns2.akamaidns.com. example.com. 300 IN A 10.0.0.1 example.com. 300 IN A 10.0.0.2 www.example.com. 300 IN A 10.0.0.1 www.example.com. 300 IN A 10.0.0.2"`, + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -512,7 +519,7 @@ www.example.com. 300 IN A 10.0.0.2"`, } })) client := mockAPIClient(t, mockServer) - err := client.PostMasterZoneFile(context.Background(), test.zone, test.masterfile) + err := client.PostMasterZoneFile(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -524,15 +531,17 @@ www.example.com. 300 IN A 10.0.0.2"`, func TestDNS_GetChangeList(t *testing.T) { tests := map[string]struct { - zone string + params GetChangeListRequest responseStatus int responseBody string expectedPath string - expectedResponse *ChangeListResponse + expectedResponse *GetChangeListResponse withError error }{ "200 OK": { - zone: "example.com", + params: GetChangeListRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -543,7 +552,7 @@ func TestDNS_GetChangeList(t *testing.T) { "stale": false }`, expectedPath: "/config-dns/v2/zones/example.com", - expectedResponse: &ChangeListResponse{ + expectedResponse: &GetChangeListResponse{ Zone: "example.com", ChangeTag: "476754f4-d605-479f-853b-db854d7254fa", ZoneVersionID: "1d9c887c-49bb-4382-87a6-d1bf690aa58f", @@ -552,7 +561,9 @@ func TestDNS_GetChangeList(t *testing.T) { }, }, "500 internal server error": { - zone: "example.com", + params: GetChangeListRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -581,7 +592,7 @@ func TestDNS_GetChangeList(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetChangeList(context.Background(), test.zone) + result, err := client.GetChangeList(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -594,7 +605,7 @@ func TestDNS_GetChangeList(t *testing.T) { func TestDNS_GetMasterZoneFile(t *testing.T) { tests := map[string]struct { - zone string + params GetMasterZoneFileRequest responseStatus int responseBody string expectedPath string @@ -602,7 +613,9 @@ func TestDNS_GetMasterZoneFile(t *testing.T) { withError error }{ "200 OK": { - zone: "example.com", + params: GetMasterZoneFileRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: ` example.com. 10000 IN SOA ns1.akamaidns.com. webmaster.example.com. 1 28800 14400 2419200 86400 @@ -623,7 +636,9 @@ func TestDNS_GetMasterZoneFile(t *testing.T) { www.example.com. 300 IN A 10.0.0.2`, }, "500 internal server error": { - zone: "example.com", + params: GetMasterZoneFileRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -654,7 +669,7 @@ func TestDNS_GetMasterZoneFile(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - result, err := client.GetMasterZoneFile(context.Background(), test.zone) + result, err := client.GetMasterZoneFile(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -667,18 +682,19 @@ func TestDNS_GetMasterZoneFile(t *testing.T) { func TestDNS_CreateZone(t *testing.T) { tests := map[string]struct { - zone ZoneCreate - query ZoneQueryString + params CreateZoneRequest responseStatus int responseBody string expectedPath string withError error }{ "201 Created": { - zone: ZoneCreate{ - Zone: "example.com", - ContractID: "1-2ABCDE", - Type: "primary", + params: CreateZoneRequest{ + CreateZone: &ZoneCreate{ + Zone: "example.com", + ContractID: "1-2ABCDE", + Type: "primary", + }, }, responseStatus: http.StatusCreated, responseBody: ` @@ -707,10 +723,12 @@ func TestDNS_CreateZone(t *testing.T) { expectedPath: "/config-dns/v2/zones?contractId=1-2ABCDE", }, "500 internal server error": { - zone: ZoneCreate{ - Zone: "example.com", - ContractID: "1-2ABCDE", - Type: "secondary", + params: CreateZoneRequest{ + CreateZone: &ZoneCreate{ + Zone: "example.com", + ContractID: "1-2ABCDE", + Type: "primary", + }, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -741,7 +759,7 @@ func TestDNS_CreateZone(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.CreateZone(context.Background(), &test.zone, test.query, true) + err := client.CreateZone(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -753,14 +771,15 @@ func TestDNS_CreateZone(t *testing.T) { func TestDNS_SaveChangelist(t *testing.T) { tests := map[string]struct { + params SaveChangeListRequest zone ZoneCreate responseStatus int responseBody string expectedPath string - withError error + withError func(*testing.T, error) }{ "201 Created": { - zone: ZoneCreate{ + params: SaveChangeListRequest{ Zone: "example.com", ContractID: "1-2ABCDE", Type: "primary", @@ -769,10 +788,10 @@ func TestDNS_SaveChangelist(t *testing.T) { expectedPath: "/config-dns/v2/changelists?zone=example.com", }, "500 internal server error": { - zone: ZoneCreate{ + params: SaveChangeListRequest{ Zone: "example.com", ContractID: "1-2ABCDE", - Type: "secondary", + Type: "primary", }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -783,11 +802,14 @@ func TestDNS_SaveChangelist(t *testing.T) { "status": 500 }`, expectedPath: "/config-dns/v2/changelists?zone=example.com", - withError: &Error{ - Type: "internal_error", - Title: "Internal Server Error", - Detail: "Error creating zone", - StatusCode: http.StatusInternalServerError, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error creating zone", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) }, }, } @@ -803,9 +825,9 @@ func TestDNS_SaveChangelist(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.SaveChangelist(context.Background(), &test.zone) + err := client.SaveChangeList(context.Background(), test.params) if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + test.withError(t, err) return } require.NoError(t, err) @@ -815,14 +837,14 @@ func TestDNS_SaveChangelist(t *testing.T) { func TestDNS_SubmitChangelist(t *testing.T) { tests := map[string]struct { - zone ZoneCreate + params SubmitChangeListRequest responseStatus int responseBody string expectedPath string withError error }{ "204 No Content": { - zone: ZoneCreate{ + params: SubmitChangeListRequest{ Zone: "example.com", ContractID: "1-2ABCDE", Type: "primary", @@ -831,7 +853,7 @@ func TestDNS_SubmitChangelist(t *testing.T) { expectedPath: "/config-dns/v2/changelists?zone=example.com", }, "500 internal server error": { - zone: ZoneCreate{ + params: SubmitChangeListRequest{ Zone: "example.com", ContractID: "1-2ABCDE", Type: "secondary", @@ -865,7 +887,7 @@ func TestDNS_SubmitChangelist(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.SubmitChangelist(context.Background(), &test.zone) + err := client.SubmitChangeList(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -877,18 +899,19 @@ func TestDNS_SubmitChangelist(t *testing.T) { func TestDNS_UpdateZone(t *testing.T) { tests := map[string]struct { - zone ZoneCreate - query ZoneQueryString + params UpdateZoneRequest responseStatus int responseBody string expectedPath string withError error }{ "200 OK": { - zone: ZoneCreate{ - Zone: "example.com", - ContractID: "1-2ABCDE", - Type: "primary", + params: UpdateZoneRequest{ + CreateZone: &ZoneCreate{ + Zone: "example.com", + ContractID: "1-2ABCDE", + Type: "primary", + }, }, responseStatus: http.StatusOK, responseBody: ` @@ -917,10 +940,12 @@ func TestDNS_UpdateZone(t *testing.T) { expectedPath: "/config-dns/v2/zones?contractId=1-2ABCDE", }, "500 internal server error": { - zone: ZoneCreate{ - Zone: "example.com", - ContractID: "1-2ABCDE", - Type: "secondary", + params: UpdateZoneRequest{ + CreateZone: &ZoneCreate{ + Zone: "example.com", + ContractID: "1-2ABCDE", + Type: "secondary", + }, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -951,7 +976,7 @@ func TestDNS_UpdateZone(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - err := client.UpdateZone(context.Background(), &test.zone, test.query) + err := client.UpdateZone(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -963,15 +988,17 @@ func TestDNS_UpdateZone(t *testing.T) { func TestDNS_GetZoneNames(t *testing.T) { tests := map[string]struct { - zone string + params GetZoneNamesRequest responseStatus int responseBody string expectedPath string - expectedResponse *ZoneNamesResponse + expectedResponse *GetZoneNamesResponse withError error }{ "200 OK": { - zone: "example.com", + params: GetZoneNamesRequest{ + Zone: "example.com", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -984,12 +1011,14 @@ func TestDNS_GetZoneNames(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/example.com/names", - expectedResponse: &ZoneNamesResponse{ + expectedResponse: &GetZoneNamesResponse{ Names: []string{"example.com", "www.example.com", "ftp.example.com", "space.example.com", "bar.example.com"}, }, }, "500 internal server error": { - zone: "example.com", + params: GetZoneNamesRequest{ + Zone: "example.com", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -1018,7 +1047,7 @@ func TestDNS_GetZoneNames(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetZoneNames(context.Background(), test.zone) + result, err := client.GetZoneNames(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -1031,17 +1060,18 @@ func TestDNS_GetZoneNames(t *testing.T) { func TestDNS_GetZoneNameTypes(t *testing.T) { tests := map[string]struct { - zone string - zname string + params GetZoneNameTypesRequest responseStatus int responseBody string expectedPath string - expectedResponse *ZoneNameTypesResponse + expectedResponse *GetZoneNameTypesResponse withError error }{ "200 OK": { - zone: "example.com", - zname: "names", + params: GetZoneNameTypesRequest{ + Zone: "example.com", + ZoneName: "names", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -1052,13 +1082,15 @@ func TestDNS_GetZoneNameTypes(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/example.com/names/www.example.com/types", - expectedResponse: &ZoneNameTypesResponse{ + expectedResponse: &GetZoneNameTypesResponse{ Types: []string{"A", "AAAA", "MX"}, }, }, "500 internal server error": { - zone: "example.com", - zname: "names", + params: GetZoneNameTypesRequest{ + Zone: "example.com", + ZoneName: "names", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -1087,7 +1119,7 @@ func TestDNS_GetZoneNameTypes(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetZoneNameTypes(context.Background(), test.zname, test.zone) + result, err := client.GetZoneNameTypes(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/dns/zonebulk.go b/pkg/dns/zonebulk.go index 0751112e..86767cc9 100644 --- a/pkg/dns/zonebulk.go +++ b/pkg/dns/zonebulk.go @@ -2,63 +2,205 @@ package dns import ( "context" + "errors" "fmt" "net/http" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // BulkZonesCreate contains a list of one or more new Zones to create + BulkZonesCreate struct { + Zones []ZoneCreate `json:"zones"` + } + + // BulkZonesResponse contains response from bulk-create request + BulkZonesResponse struct { + RequestID string `json:"requestId"` + ExpirationDate string `json:"expirationDate"` + } + + // BulkRequest contains request parameter + BulkRequest struct { + RequestID string + } + + // BulkStatusResponse contains current status of a running or completed bulk-create request + BulkStatusResponse struct { + RequestID string `json:"requestId"` + ZonesSubmitted int `json:"zonesSubmitted"` + SuccessCount int `json:"successCount"` + FailureCount int `json:"failureCount"` + IsComplete bool `json:"isComplete"` + ExpirationDate string `json:"expirationDate"` + } + + // BulkFailedZone contains information about failed zone + BulkFailedZone struct { + Zone string `json:"zone"` + FailureReason string `json:"failureReason"` + } + + // BulkCreateResultResponse contains the response from a completed bulk-create request + BulkCreateResultResponse struct { + RequestID string `json:"requestId"` + SuccessfullyCreatedZones []string `json:"successfullyCreatedZones"` + FailedZones []BulkFailedZone `json:"failedZones"` + } + + // BulkDeleteResultResponse contains the response from a completed bulk-delete request + BulkDeleteResultResponse struct { + RequestID string `json:"requestId"` + SuccessfullyDeletedZones []string `json:"successfullyDeletedZones"` + FailedZones []BulkFailedZone `json:"failedZones"` + } + + // GetBulkZoneCreateStatusRequest contains request parameters for GetBulkZoneCreateStatus + GetBulkZoneCreateStatusRequest BulkRequest + + // GetBulkZoneCreateStatusResponse contains the response data from GetBulkZoneCreateStatus operation + GetBulkZoneCreateStatusResponse struct { + RequestID string `json:"requestId"` + ZonesSubmitted int `json:"zonesSubmitted"` + SuccessCount int `json:"successCount"` + FailureCount int `json:"failureCount"` + IsComplete bool `json:"isComplete"` + ExpirationDate string `json:"expirationDate"` + } + + // GetBulkZoneDeleteStatusRequest contains request parameters for GetBulkZoneDeleteStatus + GetBulkZoneDeleteStatusRequest BulkRequest + + // GetBulkZoneDeleteStatusResponse contains the response data from GetBulkZoneDeleteStatus operation + GetBulkZoneDeleteStatusResponse struct { + RequestID string `json:"requestId"` + ZonesSubmitted int `json:"zonesSubmitted"` + SuccessCount int `json:"successCount"` + FailureCount int `json:"failureCount"` + IsComplete bool `json:"isComplete"` + ExpirationDate string `json:"expirationDate"` + } + + // GetBulkZoneCreateResultRequest contains request parameters for GetBulkZoneCreateResult + GetBulkZoneCreateResultRequest BulkRequest + + // GetBulkZoneCreateResultResponse contains the response data from GetBulkZoneCreateResult operation + GetBulkZoneCreateResultResponse struct { + RequestID string `json:"requestId"` + SuccessfullyCreatedZones []string `json:"successfullyCreatedZones"` + FailedZones []BulkFailedZone `json:"failedZones"` + } + + // GetBulkZoneDeleteResultRequest contains request parameters for GetBulkZoneDeleteResult + GetBulkZoneDeleteResultRequest BulkRequest + + // GetBulkZoneDeleteResultResponse contains the response data from GetBulkZoneDeleteResult operation + GetBulkZoneDeleteResultResponse struct { + RequestID string `json:"requestId"` + SuccessfullyDeletedZones []string `json:"successfullyDeletedZones"` + FailedZones []BulkFailedZone `json:"failedZones"` + } + + // CreateBulkZonesRequest contains request parameters for CreateBulkZones + CreateBulkZonesRequest struct { + BulkZones *BulkZonesCreate + ZoneQueryString ZoneQueryString + } + + // CreateBulkZonesResponse contains the response data from CreateBulkZones operation + CreateBulkZonesResponse struct { + RequestID string `json:"requestId"` + ExpirationDate string `json:"expirationDate"` + } + + // DeleteBulkZonesRequest contains request parameters for DeleteBulkZones + DeleteBulkZonesRequest struct { + ZonesList *ZoneNameListResponse + BypassSafetyChecks *bool + } + + // DeleteBulkZonesResponse contains the response data from DeleteBulkZones operation + DeleteBulkZonesResponse struct { + RequestID string `json:"requestId"` + ExpirationDate string `json:"expirationDate"` + } +) + +var ( + // ErrGetBulkZoneCreateStatus is returned when GetBulkZoneCreateStatus fails + ErrGetBulkZoneCreateStatus = errors.New("get bulk zone create status") + // ErrGetBulkZoneDeleteStatus is returned when GetBulkZoneDeleteStatus fails + ErrGetBulkZoneDeleteStatus = errors.New("get bulk zone delete status") + // ErrGetBulkZoneCreateResult is returned when GetBulkZoneCreateResult fails + ErrGetBulkZoneCreateResult = errors.New("get bulk zone create result") + // ErrGetBulkZoneDeleteResult is returned when GetBulkZoneDeleteResult fails + ErrGetBulkZoneDeleteResult = errors.New("get bulk zone delete result") + // ErrCreateBulkZones is returned when CreateBulkZones fails + ErrCreateBulkZones = errors.New("create bulk zones") + // ErrDeleteBulkZones is returned when DeleteBulkZones fails + ErrDeleteBulkZones = errors.New("delete bulk zones") ) -// BulkZonesCreate contains a list of one or more new Zones to create -type BulkZonesCreate struct { - Zones []*ZoneCreate `json:"zones"` +// Validate validates GetBulkZoneCreateStatusRequest +func (r GetBulkZoneCreateStatusRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "RequestID": validation.Validate(r.RequestID, validation.Required), + }) } -// BulkZonesResponse contains response from bulk-create request -type BulkZonesResponse struct { - RequestID string `json:"requestId"` - ExpirationDate string `json:"expirationDate"` +// Validate validates GetBulkZoneDeleteStatusRequest +func (r GetBulkZoneDeleteStatusRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "RequestID": validation.Validate(r.RequestID, validation.Required), + }) } -// BulkStatusResponse contains current status of a running or completed bulk-create request -type BulkStatusResponse struct { - RequestID string `json:"requestId"` - ZonesSubmitted int `json:"zonesSubmitted"` - SuccessCount int `json:"successCount"` - FailureCount int `json:"failureCount"` - IsComplete bool `json:"isComplete"` - ExpirationDate string `json:"expirationDate"` +// Validate validates GetBulkZoneCreateResultRequest +func (r GetBulkZoneCreateResultRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "RequestID": validation.Validate(r.RequestID, validation.Required), + }) } -// BulkFailedZone contains information about failed zone -type BulkFailedZone struct { - Zone string `json:"zone"` - FailureReason string `json:"failureReason"` +// Validate validates GetBulkZoneDeleteResultRequest +func (r GetBulkZoneDeleteResultRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "RequestID": validation.Validate(r.RequestID, validation.Required), + }) } -// BulkCreateResultResponse contains the response from a completed bulk-create request -type BulkCreateResultResponse struct { - RequestID string `json:"requestId"` - SuccessfullyCreatedZones []string `json:"successfullyCreatedZones"` - FailedZones []*BulkFailedZone `json:"failedZones"` +// Validate validates CreateBulkZonesRequest +func (r CreateBulkZonesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "BulkZones": validation.Validate(r.BulkZones, validation.Required), + }) } -// BulkDeleteResultResponse contains the response from a completed bulk-delete request -type BulkDeleteResultResponse struct { - RequestID string `json:"requestId"` - SuccessfullyDeletedZones []string `json:"successfullyDeletedZones"` - FailedZones []*BulkFailedZone `json:"failedZones"` +// Validate validates DeleteBulkZonesRequest +func (r DeleteBulkZonesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "ZonesList": validation.Validate(r.ZonesList, validation.Required), + }) } -func (d *dns) GetBulkZoneCreateStatus(ctx context.Context, requestID string) (*BulkStatusResponse, error) { +func (d *dns) GetBulkZoneCreateStatus(ctx context.Context, params GetBulkZoneCreateStatusRequest) (*GetBulkZoneCreateStatusResponse, error) { logger := d.Log(ctx) logger.Debug("GetBulkZoneCreateStatus") - bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/create-requests/%s", requestID) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetBulkZoneCreateStatus, ErrStructValidation, err) + } + + bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/create-requests/%s", params.RequestID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, bulkZonesURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetBulkZoneCreateStatus request: %w", err) } - var result BulkStatusResponse + var result GetBulkZoneCreateStatusResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetBulkZoneCreateStatus request failed: %w", err) @@ -71,18 +213,22 @@ func (d *dns) GetBulkZoneCreateStatus(ctx context.Context, requestID string) (*B return &result, nil } -func (d *dns) GetBulkZoneDeleteStatus(ctx context.Context, requestID string) (*BulkStatusResponse, error) { +func (d *dns) GetBulkZoneDeleteStatus(ctx context.Context, params GetBulkZoneDeleteStatusRequest) (*GetBulkZoneDeleteStatusResponse, error) { logger := d.Log(ctx) logger.Debug("GetBulkZoneDeleteStatus") - bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/delete-requests/%s", requestID) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetBulkZoneDeleteStatus, ErrStructValidation, err) + } + + bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/delete-requests/%s", params.RequestID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, bulkZonesURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetBulkZoneDeleteStatus request: %w", err) } - var result BulkStatusResponse + var result GetBulkZoneDeleteStatusResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetBulkZoneDeleteStatus request failed: %w", err) @@ -95,12 +241,16 @@ func (d *dns) GetBulkZoneDeleteStatus(ctx context.Context, requestID string) (*B return &result, nil } -func (d *dns) GetBulkZoneCreateResult(ctx context.Context, requestID string) (*BulkCreateResultResponse, error) { +func (d *dns) GetBulkZoneCreateResult(ctx context.Context, params GetBulkZoneCreateResultRequest) (*GetBulkZoneCreateResultResponse, error) { logger := d.Log(ctx) logger.Debug("GetBulkZoneCreateResult") - bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/create-requests/%s/result", requestID) - var status BulkCreateResultResponse + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetBulkZoneCreateResult, ErrStructValidation, err) + } + + bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/create-requests/%s/result", params.RequestID) + var status GetBulkZoneCreateResultResponse req, err := http.NewRequestWithContext(ctx, http.MethodGet, bulkZonesURL, nil) if err != nil { @@ -119,18 +269,22 @@ func (d *dns) GetBulkZoneCreateResult(ctx context.Context, requestID string) (*B return &status, nil } -func (d *dns) GetBulkZoneDeleteResult(ctx context.Context, requestID string) (*BulkDeleteResultResponse, error) { +func (d *dns) GetBulkZoneDeleteResult(ctx context.Context, params GetBulkZoneDeleteResultRequest) (*GetBulkZoneDeleteResultResponse, error) { logger := d.Log(ctx) logger.Debug("GetBulkZoneDeleteResult") - bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/delete-requests/%s/result", requestID) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetBulkZoneDeleteResult, ErrStructValidation, err) + } + + bulkZonesURL := fmt.Sprintf("/config-dns/v2/zones/delete-requests/%s/result", params.RequestID) req, err := http.NewRequestWithContext(ctx, http.MethodGet, bulkZonesURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetBulkZoneDeleteResult request: %w", err) } - var result BulkDeleteResultResponse + var result GetBulkZoneDeleteResultResponse resp, err := d.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetBulkZoneDeleteResult request failed: %w", err) @@ -143,13 +297,17 @@ func (d *dns) GetBulkZoneDeleteResult(ctx context.Context, requestID string) (*B return &result, nil } -func (d *dns) CreateBulkZones(ctx context.Context, bulkZones *BulkZonesCreate, zoneQueryString ZoneQueryString) (*BulkZonesResponse, error) { +func (d *dns) CreateBulkZones(ctx context.Context, params CreateBulkZonesRequest) (*CreateBulkZonesResponse, error) { logger := d.Log(ctx) logger.Debug("CreateBulkZones") - bulkZonesURL := "/config-dns/v2/zones/create-requests?contractId=" + zoneQueryString.Contract - if len(zoneQueryString.Group) > 0 { - bulkZonesURL += "&gid=" + zoneQueryString.Group + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateBulkZones, ErrStructValidation, err) + } + + bulkZonesURL := "/config-dns/v2/zones/create-requests?contractId=" + params.ZoneQueryString.Contract + if len(params.ZoneQueryString.Group) > 0 { + bulkZonesURL += "&gid=" + params.ZoneQueryString.Group } req, err := http.NewRequestWithContext(ctx, http.MethodPost, bulkZonesURL, nil) @@ -157,8 +315,8 @@ func (d *dns) CreateBulkZones(ctx context.Context, bulkZones *BulkZonesCreate, z return nil, fmt.Errorf("failed to create CreateBulkZones request: %w", err) } - var result BulkZonesResponse - resp, err := d.Exec(req, &result, bulkZones) + var result CreateBulkZonesResponse + resp, err := d.Exec(req, &result, params.BulkZones) if err != nil { return nil, fmt.Errorf("CreateBulkZones request failed: %w", err) } @@ -170,13 +328,17 @@ func (d *dns) CreateBulkZones(ctx context.Context, bulkZones *BulkZonesCreate, z return &result, nil } -func (d *dns) DeleteBulkZones(ctx context.Context, zonesList *ZoneNameListResponse, bypassSafetyChecks ...bool) (*BulkZonesResponse, error) { +func (d *dns) DeleteBulkZones(ctx context.Context, params DeleteBulkZonesRequest) (*DeleteBulkZonesResponse, error) { logger := d.Log(ctx) logger.Debug("DeleteBulkZones") + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteBulkZones, ErrStructValidation, err) + } + bulkZonesURL := "/config-dns/v2/zones/delete-requests" - if len(bypassSafetyChecks) > 0 { - bulkZonesURL += fmt.Sprintf("?bypassSafetyChecks=%t", bypassSafetyChecks[0]) + if params.BypassSafetyChecks != nil { + bulkZonesURL += fmt.Sprintf("?bypassSafetyChecks=%t", *params.BypassSafetyChecks) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, bulkZonesURL, nil) @@ -184,8 +346,8 @@ func (d *dns) DeleteBulkZones(ctx context.Context, zonesList *ZoneNameListRespon return nil, fmt.Errorf("failed to create DeleteBulkZones request: %w", err) } - var result BulkZonesResponse - resp, err := d.Exec(req, &result, zonesList) + var result DeleteBulkZonesResponse + resp, err := d.Exec(req, &result, params.ZonesList) if err != nil { return nil, fmt.Errorf("DeleteBulkZones request failed: %w", err) } diff --git a/pkg/dns/zonebulk_test.go b/pkg/dns/zonebulk_test.go index 5c5d32d2..394b6771 100644 --- a/pkg/dns/zonebulk_test.go +++ b/pkg/dns/zonebulk_test.go @@ -7,21 +7,24 @@ import ( "net/http/httptest" "testing" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDNS_GetBulkZoneCreateStatus(t *testing.T) { tests := map[string]struct { - requestID string + params GetBulkZoneCreateStatusRequest responseStatus int responseBody string expectedPath string - expectedResponse *BulkStatusResponse + expectedResponse *GetBulkZoneCreateStatusResponse withError error }{ "200 OK": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneCreateStatusRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -33,7 +36,7 @@ func TestDNS_GetBulkZoneCreateStatus(t *testing.T) { "expirationDate": "2020-10-28T17:10:04.515792Z" }`, expectedPath: "/config-dns/v2/zones/create-requests/15bc138f-8d82-451b-80b7-a56b88ffc474", - expectedResponse: &BulkStatusResponse{ + expectedResponse: &GetBulkZoneCreateStatusResponse{ RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", ZonesSubmitted: 2, SuccessCount: 0, @@ -43,7 +46,9 @@ func TestDNS_GetBulkZoneCreateStatus(t *testing.T) { }, }, "500 internal server error": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneCreateStatusRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -71,7 +76,7 @@ func TestDNS_GetBulkZoneCreateStatus(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetBulkZoneCreateStatus(context.Background(), test.requestID) + result, err := client.GetBulkZoneCreateStatus(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -84,15 +89,17 @@ func TestDNS_GetBulkZoneCreateStatus(t *testing.T) { func TestDNS_GetBulkZoneCreateResult(t *testing.T) { tests := map[string]struct { - requestID string + params GetBulkZoneCreateResultRequest responseStatus int responseBody string expectedPath string - expectedResponse *BulkCreateResultResponse + expectedResponse *GetBulkZoneCreateResultResponse withError error }{ "200 OK": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneCreateResultRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -106,10 +113,10 @@ func TestDNS_GetBulkZoneCreateResult(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/create-requests/15bc138f-8d82-451b-80b7-a56b88ffc474/result", - expectedResponse: &BulkCreateResultResponse{ + expectedResponse: &GetBulkZoneCreateResultResponse{ RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", SuccessfullyCreatedZones: make([]string, 0), - FailedZones: []*BulkFailedZone{ + FailedZones: []BulkFailedZone{ { Zone: "one.testbulk.net", FailureReason: "ZONE_ALREADY_EXISTS", @@ -118,7 +125,9 @@ func TestDNS_GetBulkZoneCreateResult(t *testing.T) { }, }, "500 internal server error": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneCreateResultRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -146,7 +155,7 @@ func TestDNS_GetBulkZoneCreateResult(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetBulkZoneCreateResult(context.Background(), test.requestID) + result, err := client.GetBulkZoneCreateResult(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -159,62 +168,67 @@ func TestDNS_GetBulkZoneCreateResult(t *testing.T) { func TestDNS_CreateBulkZones(t *testing.T) { tests := map[string]struct { + params CreateBulkZonesRequest zones BulkZonesCreate query ZoneQueryString responseStatus int responseBody string - expectedResponse *BulkZonesResponse + expectedResponse *CreateBulkZonesResponse expectedPath string withError error }{ "200 Created": { - zones: BulkZonesCreate{ - Zones: []*ZoneCreate{ - { - Zone: "one.testbulk.net", - Type: "secondary", - Comment: "testing bulk operations", - Masters: []string{"1.2.3.4", "1.2.3.10"}, - }, - { - Zone: "two.testbulk.net", - Type: "secondary", - Comment: "testing bulk operations", - Masters: []string{"1.2.3.6", "1.2.3.70"}, + params: CreateBulkZonesRequest{ + BulkZones: &BulkZonesCreate{ + Zones: []ZoneCreate{ + { + Zone: "one.testbulk.net", + Type: "secondary", + Comment: "testing bulk operations", + Masters: []string{"1.2.3.4", "1.2.3.10"}, + }, + { + Zone: "two.testbulk.net", + Type: "secondary", + Comment: "testing bulk operations", + Masters: []string{"1.2.3.6", "1.2.3.70"}, + }, }, }, + ZoneQueryString: ZoneQueryString{Contract: "1-2ABCDE", Group: "testgroup"}, }, - query: ZoneQueryString{Contract: "1-2ABCDE", Group: "testgroup"}, responseStatus: http.StatusCreated, responseBody: ` { "requestId": "93e97a28-4e05-45f4-8b9a-cebd71155949", "expirationDate": "2020-10-28T19:50:36.272668Z" }`, - expectedResponse: &BulkZonesResponse{ + expectedResponse: &CreateBulkZonesResponse{ RequestID: "93e97a28-4e05-45f4-8b9a-cebd71155949", ExpirationDate: "2020-10-28T19:50:36.272668Z", }, expectedPath: "/config-dns/v2/zones/create-requests?contractId=1-2ABCDE&gid=testgroup", }, "500 internal server error": { - zones: BulkZonesCreate{ - Zones: []*ZoneCreate{ - { - Zone: "one.testbulk.net", - Type: "secondary", - Comment: "testing bulk operations", - Masters: []string{"1.2.3.4", "1.2.3.10"}, - }, - { - Zone: "two.testbulk.net", - Type: "secondary", - Comment: "testing bulk operations", - Masters: []string{"1.2.3.6", "1.2.3.70"}, + params: CreateBulkZonesRequest{ + BulkZones: &BulkZonesCreate{ + Zones: []ZoneCreate{ + { + Zone: "one.testbulk.net", + Type: "secondary", + Comment: "testing bulk operations", + Masters: []string{"1.2.3.4", "1.2.3.10"}, + }, + { + Zone: "two.testbulk.net", + Type: "secondary", + Comment: "testing bulk operations", + Masters: []string{"1.2.3.6", "1.2.3.70"}, + }, }, }, + ZoneQueryString: ZoneQueryString{Contract: "1-2ABCDE", Group: "testgroup"}, }, - query: ZoneQueryString{Contract: "1-2ABCDE", Group: "testgroup"}, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -244,7 +258,7 @@ func TestDNS_CreateBulkZones(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - result, err := client.CreateBulkZones(context.Background(), &test.zones, test.query) + result, err := client.CreateBulkZones(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -258,15 +272,17 @@ func TestDNS_CreateBulkZones(t *testing.T) { // Bulk Delete tests func TestDNS_GetBulkZoneDeleteStatus(t *testing.T) { tests := map[string]struct { - requestID string + params GetBulkZoneDeleteStatusRequest responseStatus int responseBody string expectedPath string - expectedResponse *BulkStatusResponse + expectedResponse *GetBulkZoneDeleteStatusResponse withError error }{ "200 OK": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneDeleteStatusRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -278,7 +294,7 @@ func TestDNS_GetBulkZoneDeleteStatus(t *testing.T) { "expirationDate": "2020-10-28T17:10:04.515792Z" }`, expectedPath: "/config-dns/v2/zones/delete-requests/15bc138f-8d82-451b-80b7-a56b88ffc474", - expectedResponse: &BulkStatusResponse{ + expectedResponse: &GetBulkZoneDeleteStatusResponse{ RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", ZonesSubmitted: 2, SuccessCount: 0, @@ -288,7 +304,9 @@ func TestDNS_GetBulkZoneDeleteStatus(t *testing.T) { }, }, "500 internal server error": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneDeleteStatusRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -316,7 +334,7 @@ func TestDNS_GetBulkZoneDeleteStatus(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetBulkZoneDeleteStatus(context.Background(), test.requestID) + result, err := client.GetBulkZoneDeleteStatus(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -329,15 +347,17 @@ func TestDNS_GetBulkZoneDeleteStatus(t *testing.T) { func TestDNS_GetBulkZoneDeleteResult(t *testing.T) { tests := map[string]struct { - requestID string + params GetBulkZoneDeleteResultRequest responseStatus int responseBody string expectedPath string - expectedResponse *BulkDeleteResultResponse + expectedResponse *GetBulkZoneDeleteResultResponse withError error }{ "200 OK": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneDeleteResultRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusOK, responseBody: ` { @@ -351,10 +371,10 @@ func TestDNS_GetBulkZoneDeleteResult(t *testing.T) { ] }`, expectedPath: "/config-dns/v2/zones/delete-requests/15bc138f-8d82-451b-80b7-a56b88ffc474/result", - expectedResponse: &BulkDeleteResultResponse{ + expectedResponse: &GetBulkZoneDeleteResultResponse{ RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", SuccessfullyDeletedZones: make([]string, 0), - FailedZones: []*BulkFailedZone{ + FailedZones: []BulkFailedZone{ { Zone: "one.testbulk.net", FailureReason: "ZONE_ALREADY_EXISTS", @@ -363,7 +383,9 @@ func TestDNS_GetBulkZoneDeleteResult(t *testing.T) { }, }, "500 internal server error": { - requestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + params: GetBulkZoneDeleteResultRequest{ + RequestID: "15bc138f-8d82-451b-80b7-a56b88ffc474", + }, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -391,7 +413,7 @@ func TestDNS_GetBulkZoneDeleteResult(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetBulkZoneDeleteResult(context.Background(), test.requestID) + result, err := client.GetBulkZoneDeleteResult(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -404,36 +426,39 @@ func TestDNS_GetBulkZoneDeleteResult(t *testing.T) { func TestDNS_DeleteBulkZones(t *testing.T) { tests := map[string]struct { - zonesList ZoneNameListResponse - bypassSafety bool + params DeleteBulkZonesRequest responseStatus int responseBody string - expectedResponse *BulkZonesResponse + expectedResponse *DeleteBulkZonesResponse expectedPath string withError error }{ "200 Created": { - zonesList: ZoneNameListResponse{ - Zones: []string{"one.testbulk.net", "two.testbulk.net"}, + params: DeleteBulkZonesRequest{ + ZonesList: &ZoneNameListResponse{ + Zones: []string{"one.testbulk.net", "two.testbulk.net"}, + }, + BypassSafetyChecks: ptr.To(true), }, - bypassSafety: true, responseStatus: http.StatusCreated, responseBody: ` { "requestId": "93e97a28-4e05-45f4-8b9a-cebd71155949", "expirationDate": "2020-10-28T19:50:36.272668Z" }`, - expectedResponse: &BulkZonesResponse{ + expectedResponse: &DeleteBulkZonesResponse{ RequestID: "93e97a28-4e05-45f4-8b9a-cebd71155949", ExpirationDate: "2020-10-28T19:50:36.272668Z", }, expectedPath: "/config-dns/v2/zones/delete-requests?bypassSafetyChecks=true", }, "500 internal server error": { - zonesList: ZoneNameListResponse{ - Zones: []string{"one.testbulk.net", "two.testbulk.net"}, + params: DeleteBulkZonesRequest{ + ZonesList: &ZoneNameListResponse{ + Zones: []string{"one.testbulk.net", "two.testbulk.net"}, + }, + BypassSafetyChecks: ptr.To(true), }, - bypassSafety: true, responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -463,7 +488,7 @@ func TestDNS_DeleteBulkZones(t *testing.T) { } })) client := mockAPIClient(t, mockServer) - result, err := client.DeleteBulkZones(context.Background(), &test.zonesList, test.bypassSafety) + result, err := client.DeleteBulkZones(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/edgeworkers/activations.go b/pkg/edgeworkers/activations.go index 23fcd8ec..070872bc 100644 --- a/pkg/edgeworkers/activations.go +++ b/pkg/edgeworkers/activations.go @@ -11,29 +11,6 @@ import ( ) type ( - // Activations is an edgeworkers activations API interface - Activations interface { - // ListActivations lists all activations for an EdgeWorker - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-activations-1 - ListActivations(context.Context, ListActivationsRequest) (*ListActivationsResponse, error) - - // GetActivation fetches an EdgeWorker activation by id - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-activation-1 - GetActivation(context.Context, GetActivationRequest) (*Activation, error) - - // ActivateVersion activates an EdgeWorker version on a given network - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-activations-1 - ActivateVersion(context.Context, ActivateVersionRequest) (*Activation, error) - - // CancelPendingActivation cancels pending activation with a given id - // - // See: https://techdocs.akamai.com/edgeworkers/reference/cancel-activation - CancelPendingActivation(context.Context, CancelActivationRequest) (*Activation, error) - } - // ListActivationsRequest contains parameters used to list activations ListActivationsRequest struct { EdgeWorkerID int @@ -147,7 +124,7 @@ var ( ErrCancelActivation = errors.New("cancel activation") ) -func (e edgeworkers) ListActivations(ctx context.Context, params ListActivationsRequest) (*ListActivationsResponse, error) { +func (e *edgeworkers) ListActivations(ctx context.Context, params ListActivationsRequest) (*ListActivationsResponse, error) { logger := e.Log(ctx) logger.Debug("ListActivations") @@ -184,7 +161,7 @@ func (e edgeworkers) ListActivations(ctx context.Context, params ListActivations return &result, nil } -func (e edgeworkers) GetActivation(ctx context.Context, params GetActivationRequest) (*Activation, error) { +func (e *edgeworkers) GetActivation(ctx context.Context, params GetActivationRequest) (*Activation, error) { logger := e.Log(ctx) logger.Debug("GetActivation") @@ -212,7 +189,7 @@ func (e edgeworkers) GetActivation(ctx context.Context, params GetActivationRequ return &result, nil } -func (e edgeworkers) ActivateVersion(ctx context.Context, params ActivateVersionRequest) (*Activation, error) { +func (e *edgeworkers) ActivateVersion(ctx context.Context, params ActivateVersionRequest) (*Activation, error) { logger := e.Log(ctx) logger.Debug("ActivateVersion") @@ -241,7 +218,7 @@ func (e edgeworkers) ActivateVersion(ctx context.Context, params ActivateVersion return &result, nil } -func (e edgeworkers) CancelPendingActivation(ctx context.Context, params CancelActivationRequest) (*Activation, error) { +func (e *edgeworkers) CancelPendingActivation(ctx context.Context, params CancelActivationRequest) (*Activation, error) { logger := e.Log(ctx) logger.Debug("CancelPendingActivation") diff --git a/pkg/edgeworkers/contracts.go b/pkg/edgeworkers/contracts.go index 40cec967..c6c0fd59 100644 --- a/pkg/edgeworkers/contracts.go +++ b/pkg/edgeworkers/contracts.go @@ -8,14 +8,6 @@ import ( ) type ( - // Contracts is an edgeworkers contracts API interface - Contracts interface { - // ListContracts lists contract IDs that can be used to list resource tiers - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-contracts-1 - ListContracts(context.Context) (*ListContractsResponse, error) - } - // ListContractsResponse represents a response object returned by ListContracts ListContractsResponse struct { ContractIDs []string `json:"contractIds"` diff --git a/pkg/edgeworkers/deactivations.go b/pkg/edgeworkers/deactivations.go index 87a17567..625e78e8 100644 --- a/pkg/edgeworkers/deactivations.go +++ b/pkg/edgeworkers/deactivations.go @@ -11,24 +11,6 @@ import ( ) type ( - // Deactivations is an EdgeWorkers deactivations API interface - Deactivations interface { - // ListDeactivations lists all deactivations for a given EdgeWorker ID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-deactivations-1 - ListDeactivations(context.Context, ListDeactivationsRequest) (*ListDeactivationsResponse, error) - - // GetDeactivation gets details for a specific deactivation - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-deactivation-1 - GetDeactivation(context.Context, GetDeactivationRequest) (*Deactivation, error) - - // DeactivateVersion deactivates an existing EdgeWorker version on the akamai network - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-deactivations-1 - DeactivateVersion(context.Context, DeactivateVersionRequest) (*Deactivation, error) - } - // Deactivation is the response returned by GetDeactivation, DeactivateVersion and ListDeactivation Deactivation struct { EdgeWorkerID int `json:"edgeWorkerId"` diff --git a/pkg/edgeworkers/edgekv_access_tokens.go b/pkg/edgeworkers/edgekv_access_tokens.go index 244d937e..b067e7cb 100644 --- a/pkg/edgeworkers/edgekv_access_tokens.go +++ b/pkg/edgeworkers/edgekv_access_tokens.go @@ -12,41 +12,18 @@ import ( ) type ( - // EdgeKVAccessTokens is EdgeKV access token API interface - EdgeKVAccessTokens interface { - // CreateEdgeKVAccessToken generates EdgeKV specific access token - // - // See: https://techdocs.akamai.com/edgekv/reference/post-tokens - CreateEdgeKVAccessToken(context.Context, CreateEdgeKVAccessTokenRequest) (*CreateEdgeKVAccessTokenResponse, error) - - // GetEdgeKVAccessToken retrieves an EdgeKV access token - // - // See: https://techdocs.akamai.com/edgekv/reference/get-token - GetEdgeKVAccessToken(context.Context, GetEdgeKVAccessTokenRequest) (*GetEdgeKVAccessTokenResponse, error) - - // ListEdgeKVAccessTokens lists EdgeKV access tokens - // - // See: https://techdocs.akamai.com/edgekv/reference/get-tokens - ListEdgeKVAccessTokens(context.Context, ListEdgeKVAccessTokensRequest) (*ListEdgeKVAccessTokensResponse, error) - - // DeleteEdgeKVAccessToken revokes an EdgeKV access token - // - // See: https://techdocs.akamai.com/edgekv/reference/delete-token - DeleteEdgeKVAccessToken(context.Context, DeleteEdgeKVAccessTokenRequest) (*DeleteEdgeKVAccessTokenResponse, error) - } - // CreateEdgeKVAccessTokenRequest contains parameters used to create EdgeKV access token CreateEdgeKVAccessTokenRequest struct { // Whether to allow this token access to the Akamai production network AllowOnProduction bool `json:"allowOnProduction"` // Whether to allow this token access to the Akamai staging network AllowOnStaging bool `json:"allowOnStaging"` - // Desired token expiry date in ISO format. Expiry can be up to 6 months from creation. - Expiry string `json:"expiry"` // Friendly name of the token. Used when retrieving tokens by name. Name string `json:"name"` // A list of namespace identifiers the token should have access to, plus the associated read, write, delete permissions NamespacePermissions NamespacePermissions `json:"namespacePermissions"` + // A set of EdgeWorker IDs authorized to access EdgeKV via the token. By default, if you omit this array, the token authorizes access for all IDs. + RestrictToEdgeWorkerIDs []string `json:"restrictToEdgeWorkerIds"` } // NamespacePermissions represents mapping between namespaces and permissions @@ -66,18 +43,33 @@ type ( Name string `json:"name"` // Internally generated unique identifier for the access token UUID string `json:"uuid"` + // The IN_PROGRESS status indicates token activation is still in progress, + // and it's not yet possible to make successful EdgeKV requests from EdgeWorkers that use the token. + // Once activation completes, status is COMPLETE. + // Otherwise, a value of ERROR indicates a problem that prevented activation. + TokenActivationStatus *string `json:"tokenActivationStatus"` + // Initial token creation date in ISO 8601 format. + IssueDate *string `json:"issueDate"` + // Most recent token refresh date in ISO 8601 format. A null value indicates the token has not yet been refreshed. + LatestRefreshDate *string `json:"latestRefreshDate"` + // Next scheduled date of the token refresh in ISO 8601 format. + NextScheduledRefreshDate *string `json:"nextScheduledRefreshDate"` } // CreateEdgeKVAccessTokenResponse contains response from EdgeKV access token creation CreateEdgeKVAccessTokenResponse struct { - // The expiry date - Expiry string `json:"expiry"` - // The name assigned to the access token. You can't modify an access token name. - Name string `json:"name"` - // Internally generated unique identifier for the access token - UUID string `json:"uuid"` - // The access token details - Value string `json:"value"` + AllowOnProduction bool `json:"allowOnProduction"` + AllowOnStaging bool `json:"allowOnStaging"` + CPCode string `json:"cpcode"` + Expiry string `json:"expiry"` + IssueDate string `json:"issueDate"` + LatestRefreshDate *string `json:"latestRefreshDate"` + Name string `json:"name"` + NamespacePermissions NamespacePermissions `json:"namespacePermissions"` + NextScheduledRefreshDate string `json:"nextScheduledRefreshDate"` + RestrictToEdgeWorkerIDs []string `json:"restrictToEdgeWorkerIds"` + TokenActivationStatus string `json:"tokenActivationStatus"` + UUID string `json:"uuid"` } // GetEdgeKVAccessTokenRequest represents an TokenName object @@ -129,7 +121,6 @@ func (c CreateEdgeKVAccessTokenRequest) Validate() error { return validation.Errors{ "AllowOnProduction": validation.Validate(c.AllowOnProduction, validation.Required.When(c.AllowOnStaging == false).Error("at least one of AllowOnProduction or AllowOnStaging has to be provided")), "AllowOnStaging": validation.Validate(c.AllowOnStaging, validation.Required.When(c.AllowOnProduction == false).Error("at least one of AllowOnProduction or AllowOnStaging has to be provided")), - "Expiry": validation.Validate(c.Expiry, validation.Required, validation.Date("2006-01-02").Error("the time format should be provided as per ISO-8601")), "Name": validation.Validate(c.Name, validation.Required, validation.Length(1, 32)), "NamespacePermissions.Names": validation.Validate(namespaces, validation.Required, validation.Each(validation.Required)), "NamespacePermissions": validation.Validate(c.NamespacePermissions, diff --git a/pkg/edgeworkers/edgekv_access_tokens_test.go b/pkg/edgeworkers/edgekv_access_tokens_test.go index f3710420..b00bb965 100644 --- a/pkg/edgeworkers/edgekv_access_tokens_test.go +++ b/pkg/edgeworkers/edgekv_access_tokens_test.go @@ -9,6 +9,7 @@ import ( "strconv" "testing" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,35 +28,67 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: false, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, "devexp-jsmith-test": []Permission{"r", "w"}, }, + RestrictToEdgeWorkerIDs: []string{ + "1234", + "5678", + }, }, - expectedRequestBody: `{"allowOnProduction":false,"allowOnStaging":true,"expiry":"2022-03-30","name":"devexp-token-1","namespacePermissions":{"default":["r","w","d"],"devexp-jsmith-test":["r","w"]}}`, + expectedRequestBody: `{"allowOnProduction":false,"allowOnStaging":true,"name":"devexp-token-1","namespacePermissions":{"default":["r","w","d"],"devexp-jsmith-test":["r","w"]},"restrictToEdgeWorkerIds":["1234","5678"]}`, responseStatus: http.StatusOK, responseBody: ` { "name": "devexp-token-1", "uuid": "1ab0e94b-c47e-568e-ab3e-1921ffcefe0c", - "expiry": "2022-03-30", - "value": "eyJ0eXAiOxJKV1QxLCJhbGciOiJSUzI1NiJ9.eyJld2lkcyI6ImFsbCIsInN1YiI6IjUwMCIsIm5hbWVzcGFjZS1kZWZhdWx0IjpbInIiLCJkIiwidyJdLCJjcGMiOiI5NzEwNTIiLCJpc3MiOiJha2FtYWkuY29tL0VkZ2VEQi9QdWxzYXIvdjAuMTEuMCIsIm5hbWVzcGFjZS1kZXZleHAtcm9iZXJ0by10ZXN0IjpbInIiLCJ3Il0sImV4cCI6MTY0ODY4NDc5OSwiZW52IjpbInAiLCJzIl0sImlhdCI6MTY0MDg1ODIzNywianRpIjoiMTBiMGU5NGItYzQ3ZS01NjhlLWFiM2UtMTkyMWZmY2VmZTBjIiwicmVxaWQiOiJha2FtYWkiLCJub2VjbCI6dHJ1ZX0.AZfP-VFqDKNWcu1Or73EFfjG_GBDdJUP81Zs0BnNs_bScc8oyBAEiBjxwEsUxrvRRr7rSu-BxFjiDpxx5DlfbgEwd8H2DFV08cfQFqs7aab4WYLrx4ZweD9Hbg2gGLA-dRAbtSrq_FQKQysOvO2ymPn13E78PvK96t8r4cnN1irXbfyBUOXOE3OVOAKsk-w0Ig7qFDa_4o6YyDMPTpwEQ34T1cVqRYStIVzjSaCwgSfdaQG5qzTzTlFoDzG24tz8YlLgoM5OQf9xgsTsisCOF2jf44VWMu2S0e6MIC5gg7zXx7X2t59Y8TsAd0VqqB37y0AzEXkJblbZUlO9HcGebg" + "allowOnProduction": true, + "allowOnStaging": false, + "cpcode": "1234567", + "expiry": "9999-12-31", + "issueDate": "2022-04-30", + "namespacePermissions": { + "default": [ + "r", + "w", + "d" + ], + "devexp-jsmith-test": [ + "r", + "w" + ] + }, + "nextScheduledRefreshDate": "2022-06-30", + "restrictToEdgeWorkerIds": [ + "1234", + "5678" + ], + "tokenActivationStatus": "IN_PROGRESS" }`, expectedPath: "/edgekv/v1/tokens", expectedResponse: &CreateEdgeKVAccessTokenResponse{ - Name: "devexp-token-1", - UUID: "1ab0e94b-c47e-568e-ab3e-1921ffcefe0c", - Expiry: "2022-03-30", - Value: "eyJ0eXAiOxJKV1QxLCJhbGciOiJSUzI1NiJ9.eyJld2lkcyI6ImFsbCIsInN1YiI6IjUwMCIsIm5hbWVzcGFjZS1kZWZhdWx0IjpbInIiLCJkIiwidyJdLCJjcGMiOiI5NzEwNTIiLCJpc3MiOiJha2FtYWkuY29tL0VkZ2VEQi9QdWxzYXIvdjAuMTEuMCIsIm5hbWVzcGFjZS1kZXZleHAtcm9iZXJ0by10ZXN0IjpbInIiLCJ3Il0sImV4cCI6MTY0ODY4NDc5OSwiZW52IjpbInAiLCJzIl0sImlhdCI6MTY0MDg1ODIzNywianRpIjoiMTBiMGU5NGItYzQ3ZS01NjhlLWFiM2UtMTkyMWZmY2VmZTBjIiwicmVxaWQiOiJha2FtYWkiLCJub2VjbCI6dHJ1ZX0.AZfP-VFqDKNWcu1Or73EFfjG_GBDdJUP81Zs0BnNs_bScc8oyBAEiBjxwEsUxrvRRr7rSu-BxFjiDpxx5DlfbgEwd8H2DFV08cfQFqs7aab4WYLrx4ZweD9Hbg2gGLA-dRAbtSrq_FQKQysOvO2ymPn13E78PvK96t8r4cnN1irXbfyBUOXOE3OVOAKsk-w0Ig7qFDa_4o6YyDMPTpwEQ34T1cVqRYStIVzjSaCwgSfdaQG5qzTzTlFoDzG24tz8YlLgoM5OQf9xgsTsisCOF2jf44VWMu2S0e6MIC5gg7zXx7X2t59Y8TsAd0VqqB37y0AzEXkJblbZUlO9HcGebg", + Name: "devexp-token-1", + UUID: "1ab0e94b-c47e-568e-ab3e-1921ffcefe0c", + Expiry: "9999-12-31", + AllowOnProduction: true, + AllowOnStaging: false, + CPCode: "1234567", + IssueDate: "2022-04-30", + NamespacePermissions: NamespacePermissions{ + "default": []Permission{"r", "w", "d"}, + "devexp-jsmith-test": []Permission{"r", "w"}, + }, + NextScheduledRefreshDate: "2022-06-30", + RestrictToEdgeWorkerIDs: []string{"1234", "5678"}, + TokenActivationStatus: "IN_PROGRESS", }, }, "at least one allow is required": { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: false, AllowOnStaging: false, - Expiry: "2022-03-30", Name: "name", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, @@ -68,7 +101,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, @@ -76,23 +108,10 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { }, }, withError: ErrStructValidation, }, - "invalid date": { - params: CreateEdgeKVAccessTokenRequest{ - AllowOnProduction: true, - AllowOnStaging: true, - Expiry: "30/09/2021", - Name: "name", - NamespacePermissions: NamespacePermissions{ - "default": []Permission{"r", "w", "d"}, - "devexp-jsmith-test": []Permission{"r", "w"}, - }, - }, withError: ErrStructValidation, - }, "invalid permission": { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{"a", "w", "d"}, @@ -103,7 +122,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "": []Permission{"r", "w", "d"}, @@ -114,7 +132,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{}, @@ -125,7 +142,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", }, withError: ErrStructValidation, }, @@ -133,7 +149,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, @@ -169,7 +184,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, @@ -208,7 +222,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, @@ -245,7 +258,6 @@ func TestCreateEdgeKVAccessToken(t *testing.T) { params: CreateEdgeKVAccessTokenRequest{ AllowOnProduction: true, AllowOnStaging: true, - Expiry: "2022-03-30", Name: "devexp-token-1", NamespacePermissions: NamespacePermissions{ "default": []Permission{"r", "w", "d"}, @@ -325,15 +337,45 @@ func TestGetEdgeKVAccessToken(t *testing.T) { { "name": "devexp-token-1", "uuid": "10b0e94b-c47e-568e-ab3e-1921ffcefe0c", - "expiry": "2022-03-30", - "value": "eyJ0eXAxOxJKV1QxLCJhbGciOiJSUzI1NiJ9.eyJld2lkcyI6ImFsbCIsInN1YiI6IjUwMCIsIm5hbWVzcGFjZS1kZWZhdWx0IjpbInIiLCJkIiwidyJdLCJjcGMiOiI5NzEwNTIiLCJpc3MiOiJha2FtYWkuY29tL0VkZ2VEQi9QdWxzYXIvdjAuMTEuMCIsIm5hbWVzcGFjZS1kZXZleHAtcm9iZXJ0by10ZXN0IjpbInIiLCJ3Il0sImV4cCI6MTY0ODY4NDc5OSwiZW52IjpbInAiLCJzIl0sImlhdCI6MTY0MDg1ODIzNywianRpIjoiMTBiMGU5NGItYzQ3ZS01NjhlLWFiM2UtMTkyMWZmY2VmZTBjIiwicmVxaWQiOiJha2FtYWkiLCJub2VjbCI6dHJ1ZX0.AZfP-VFqDKNWcu1Or73EFfjG_GBDdJUP81Zs0BnNs_bScc8oyBAEiBjxwEsUxrvRRr7rSu-BxFjiDpxx5DlfbgEwd8H2DFV08cfQFqs7aab4WYLrx4ZweD9Hbg2gGLA-dRAbtSrq_FQKQysOvO2ymPn13E78PvK96t8r4cnN1irXbfyBUOXOE3OVOAKsk-w0Ig7qFDa_4o6YyDMPTpwEQ34T1cVqRYStIVzjSaCwgSfdaQG5qzTzTlFoDzG24tz8YlLgoM5OQf9xgsTsisCOF2jf44VWMu2S0e6MIC5gg7zXx7X2t59Y8TsAd0VqqB37y0AzEXkJblbZUlO9HcGebg" + "allowOnProduction": true, + "allowOnStaging": false, + "cpcode": "1234567", + "expiry": "9999-12-31", + "issueDate": "2022-04-30", + "namespacePermissions": { + "default": [ + "r", + "w", + "d" + ], + "devexp-jsmith-test": [ + "r", + "w" + ] + }, + "nextScheduledRefreshDate": "2022-06-30", + "restrictToEdgeWorkerIds": [ + "1234", + "5678" + ], + "tokenActivationStatus": "IN_PROGRESS" }`, expectedPath: "/edgekv/v1/tokens/devexp-token-1", expectedResponse: &GetEdgeKVAccessTokenResponse{ - Name: "devexp-token-1", - UUID: "10b0e94b-c47e-568e-ab3e-1921ffcefe0c", - Expiry: "2022-03-30", - Value: "eyJ0eXAxOxJKV1QxLCJhbGciOiJSUzI1NiJ9.eyJld2lkcyI6ImFsbCIsInN1YiI6IjUwMCIsIm5hbWVzcGFjZS1kZWZhdWx0IjpbInIiLCJkIiwidyJdLCJjcGMiOiI5NzEwNTIiLCJpc3MiOiJha2FtYWkuY29tL0VkZ2VEQi9QdWxzYXIvdjAuMTEuMCIsIm5hbWVzcGFjZS1kZXZleHAtcm9iZXJ0by10ZXN0IjpbInIiLCJ3Il0sImV4cCI6MTY0ODY4NDc5OSwiZW52IjpbInAiLCJzIl0sImlhdCI6MTY0MDg1ODIzNywianRpIjoiMTBiMGU5NGItYzQ3ZS01NjhlLWFiM2UtMTkyMWZmY2VmZTBjIiwicmVxaWQiOiJha2FtYWkiLCJub2VjbCI6dHJ1ZX0.AZfP-VFqDKNWcu1Or73EFfjG_GBDdJUP81Zs0BnNs_bScc8oyBAEiBjxwEsUxrvRRr7rSu-BxFjiDpxx5DlfbgEwd8H2DFV08cfQFqs7aab4WYLrx4ZweD9Hbg2gGLA-dRAbtSrq_FQKQysOvO2ymPn13E78PvK96t8r4cnN1irXbfyBUOXOE3OVOAKsk-w0Ig7qFDa_4o6YyDMPTpwEQ34T1cVqRYStIVzjSaCwgSfdaQG5qzTzTlFoDzG24tz8YlLgoM5OQf9xgsTsisCOF2jf44VWMu2S0e6MIC5gg7zXx7X2t59Y8TsAd0VqqB37y0AzEXkJblbZUlO9HcGebg", + Name: "devexp-token-1", + UUID: "10b0e94b-c47e-568e-ab3e-1921ffcefe0c", + AllowOnProduction: true, + AllowOnStaging: false, + CPCode: "1234567", + Expiry: "9999-12-31", + IssueDate: "2022-04-30", + NamespacePermissions: NamespacePermissions{ + "default": []Permission{"r", "w", "d"}, + "devexp-jsmith-test": []Permission{"r", "w"}, + }, + NextScheduledRefreshDate: "2022-06-30", + RestrictToEdgeWorkerIDs: []string{"1234", "5678"}, + TokenActivationStatus: "IN_PROGRESS", }, }, "missing token name": { @@ -472,47 +514,43 @@ func TestListEdgeKVAccessTokens(t *testing.T) { { "name": "my_token", "uuid": "8301fef4-80e5-5efb-9bfb-8f5869a5df7b", - "expiry": "2022-03-30" + "expiry": "2022-03-30", + "issueDate": "2022-01-30", + "latestRefreshDate": "2022-03-30", + "nextScheduledRefreshDate": "2022-05-30", + "tokenActivationStatus": "COMPLETE" }, { "name": "token1", "uuid": "5b5d3bfb-8d2e-5fbb-858d-33807edc9554", - "expiry": "2022-01-22" - }, - { - "name": "token2", - "uuid": "62181cfe-268a-5302-8834-67c67ec86efd", - "expiry": "2022-01-22" - }, - { - "name": "token3", - "uuid": "edb02678-ae1c-564c-8f73-c977ffdfe016", - "expiry": "2022-01-22" + "expiry": "2022-01-22", + "issueDate": "2022-04-30", + "latestRefreshDate": null, + "nextScheduledRefreshDate": "2022-06-30", + "tokenActivationStatus": "IN_PROGRESS" } - ] + ] }`, expectedPath: "/edgekv/v1/tokens", expectedResponse: &ListEdgeKVAccessTokensResponse{ []EdgeKVAccessToken{ { - Name: "my_token", - UUID: "8301fef4-80e5-5efb-9bfb-8f5869a5df7b", - Expiry: "2022-03-30", - }, - { - Name: "token1", - UUID: "5b5d3bfb-8d2e-5fbb-858d-33807edc9554", - Expiry: "2022-01-22", + Name: "my_token", + UUID: "8301fef4-80e5-5efb-9bfb-8f5869a5df7b", + Expiry: "2022-03-30", + IssueDate: ptr.To("2022-01-30"), + LatestRefreshDate: ptr.To("2022-03-30"), + NextScheduledRefreshDate: ptr.To("2022-05-30"), + TokenActivationStatus: ptr.To("COMPLETE"), }, { - Name: "token2", - UUID: "62181cfe-268a-5302-8834-67c67ec86efd", - Expiry: "2022-01-22", - }, - { - Name: "token3", - UUID: "edb02678-ae1c-564c-8f73-c977ffdfe016", - Expiry: "2022-01-22", + Name: "token1", + UUID: "5b5d3bfb-8d2e-5fbb-858d-33807edc9554", + Expiry: "2022-01-22", + IssueDate: ptr.To("2022-04-30"), + LatestRefreshDate: nil, + NextScheduledRefreshDate: ptr.To("2022-06-30"), + TokenActivationStatus: ptr.To("IN_PROGRESS"), }, }, }, @@ -528,27 +566,20 @@ func TestListEdgeKVAccessTokens(t *testing.T) { { "name": "my_token", "uuid": "8301fef4-80e5-5efb-9bfb-8f5869a5df7b", - "expiry": "2022-03-30" + "expiry": "2022-03-30", + "issueDate": "2022-01-30", + "latestRefreshDate": "2022-03-30", + "nextScheduledRefreshDate": "2022-05-30", + "tokenActivationStatus": "COMPLETE" }, { "name": "token1", "uuid": "5b5d3bfb-8d2e-5fbb-858d-33807edc9554", - "expiry": "2022-01-22" - }, - { - "name": "token2", - "uuid": "62181cfe-268a-5302-8834-67c67ec86efd", - "expiry": "2022-01-22" - }, - { - "name": "token3", - "uuid": "edb02678-ae1c-564c-8f73-c977ffdfe016", - "expiry": "2022-01-22" - }, - { - "name": "preexistingTokenTest", - "uuid": "7a14da8c-1709-570b-9535-2cc6e2ee5a8a", - "expiry": "2021-12-21" + "expiry": "2022-01-22", + "issueDate": "2022-04-30", + "latestRefreshDate": null, + "nextScheduledRefreshDate": "2022-06-30", + "tokenActivationStatus": "IN_PROGRESS" } ] }`, @@ -556,29 +587,22 @@ func TestListEdgeKVAccessTokens(t *testing.T) { expectedResponse: &ListEdgeKVAccessTokensResponse{ []EdgeKVAccessToken{ { - Name: "my_token", - UUID: "8301fef4-80e5-5efb-9bfb-8f5869a5df7b", - Expiry: "2022-03-30", - }, - { - Name: "token1", - UUID: "5b5d3bfb-8d2e-5fbb-858d-33807edc9554", - Expiry: "2022-01-22", - }, - { - Name: "token2", - UUID: "62181cfe-268a-5302-8834-67c67ec86efd", - Expiry: "2022-01-22", - }, - { - Name: "token3", - UUID: "edb02678-ae1c-564c-8f73-c977ffdfe016", - Expiry: "2022-01-22", + Name: "my_token", + UUID: "8301fef4-80e5-5efb-9bfb-8f5869a5df7b", + Expiry: "2022-03-30", + IssueDate: ptr.To("2022-01-30"), + LatestRefreshDate: ptr.To("2022-03-30"), + NextScheduledRefreshDate: ptr.To("2022-05-30"), + TokenActivationStatus: ptr.To("COMPLETE"), }, { - Name: "preexistingTokenTest", - UUID: "7a14da8c-1709-570b-9535-2cc6e2ee5a8a", - Expiry: "2021-12-21", + Name: "token1", + UUID: "5b5d3bfb-8d2e-5fbb-858d-33807edc9554", + Expiry: "2022-01-22", + IssueDate: ptr.To("2022-04-30"), + LatestRefreshDate: nil, + NextScheduledRefreshDate: ptr.To("2022-06-30"), + TokenActivationStatus: ptr.To("IN_PROGRESS"), }, }, }, diff --git a/pkg/edgeworkers/edgekv_groups.go b/pkg/edgeworkers/edgekv_groups.go index a951ee80..ae1c381e 100644 --- a/pkg/edgeworkers/edgekv_groups.go +++ b/pkg/edgeworkers/edgekv_groups.go @@ -6,20 +6,12 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Groups is EdgeKV groups within a namespace API interface - Groups interface { - // ListGroupsWithinNamespace lists group identifiers created when writing items to a namespace - // - // See: https://techdocs.akamai.com/edgekv/reference/get-groups - ListGroupsWithinNamespace(context.Context, ListGroupsWithinNamespaceRequest) ([]string, error) - } - // ListGroupsWithinNamespaceRequest contains parameters used to get groups within a namespace ListGroupsWithinNamespaceRequest struct { Network NamespaceNetwork diff --git a/pkg/edgeworkers/edgekv_initialize.go b/pkg/edgeworkers/edgekv_initialize.go index 36049e0e..5e4fe4e0 100644 --- a/pkg/edgeworkers/edgekv_initialize.go +++ b/pkg/edgeworkers/edgekv_initialize.go @@ -9,18 +9,6 @@ import ( // EdgeKVInitialize is EdgeKV Initialize API interface type ( - EdgeKVInitialize interface { - // InitializeEdgeKV Initialize the EdgeKV database - // - // See: https://techdocs.akamai.com/edgekv/reference/put-initialize - InitializeEdgeKV(ctx context.Context) (*EdgeKVInitializationStatus, error) - - // GetEdgeKVInitializationStatus is used to check on the current initialization status - // - // See: https://techdocs.akamai.com/edgekv/reference/get-initialize - GetEdgeKVInitializationStatus(ctx context.Context) (*EdgeKVInitializationStatus, error) - } - // EdgeKVInitializationStatus represents a response object returned by InitializeEdgeKV and GetEdgeKVInitializeStatus EdgeKVInitializationStatus struct { AccountStatus string `json:"accountStatus"` diff --git a/pkg/edgeworkers/edgekv_items.go b/pkg/edgeworkers/edgekv_items.go index f1dfe506..c233c5eb 100644 --- a/pkg/edgeworkers/edgekv_items.go +++ b/pkg/edgeworkers/edgekv_items.go @@ -13,29 +13,6 @@ import ( ) type ( - // EdgeKVItems is EdgeKV Item API interface - EdgeKVItems interface { - // ListItems lists items in EdgeKV group - // - // See: https://techdocs.akamai.com/edgekv/reference/get-group-1 - ListItems(context.Context, ListItemsRequest) (*ListItemsResponse, error) - - // GetItem reads an item from EdgeKV group - // - // See: https://techdocs.akamai.com/edgekv/reference/get-item - GetItem(context.Context, GetItemRequest) (*Item, error) - - // UpsertItem creates or updates an item in EdgeKV group - // - // See: https://techdocs.akamai.com/edgekv/reference/put-item - UpsertItem(context.Context, UpsertItemRequest) (*string, error) - - // DeleteItem deletes an item from EdgeKV group - // - // See: https://techdocs.akamai.com/edgekv/reference/delete-item - DeleteItem(context.Context, DeleteItemRequest) (*string, error) - } - // ListItemsRequest represents the request params used to list items ListItemsRequest struct { ItemsRequestParams diff --git a/pkg/edgeworkers/edgekv_namespaces.go b/pkg/edgeworkers/edgekv_namespaces.go index afbefc8b..fa497e35 100644 --- a/pkg/edgeworkers/edgekv_namespaces.go +++ b/pkg/edgeworkers/edgekv_namespaces.go @@ -11,29 +11,6 @@ import ( ) type ( - // EdgeKVNamespaces is an EdgeKV namespaces API interface - EdgeKVNamespaces interface { - // ListEdgeKVNamespaces lists all namespaces in the given network - // - // See: https://techdocs.akamai.com/edgekv/reference/get-namespaces - ListEdgeKVNamespaces(context.Context, ListEdgeKVNamespacesRequest) (*ListEdgeKVNamespacesResponse, error) - - // GetEdgeKVNamespace fetches a namespace by name - // - // See: https://techdocs.akamai.com/edgekv/reference/get-namespace - GetEdgeKVNamespace(context.Context, GetEdgeKVNamespaceRequest) (*Namespace, error) - - // CreateEdgeKVNamespace creates a namespace on the given network - // - // See: https://techdocs.akamai.com/edgekv/reference/post-namespace - CreateEdgeKVNamespace(context.Context, CreateEdgeKVNamespaceRequest) (*Namespace, error) - - // UpdateEdgeKVNamespace updates a namespace - // - // See: https://techdocs.akamai.com/edgekv/reference/put-namespace - UpdateEdgeKVNamespace(context.Context, UpdateEdgeKVNamespaceRequest) (*Namespace, error) - } - // ListEdgeKVNamespacesRequest contains path parameters used to list namespaces ListEdgeKVNamespacesRequest struct { Network NamespaceNetwork diff --git a/pkg/edgeworkers/edgekv_namespaces_test.go b/pkg/edgeworkers/edgekv_namespaces_test.go index 8b18321a..275f759d 100644 --- a/pkg/edgeworkers/edgekv_namespaces_test.go +++ b/pkg/edgeworkers/edgekv_namespaces_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -120,21 +120,21 @@ func TestListNamespaces(t *testing.T) { Namespaces: []Namespace{ { Name: "testNs_1", - Retention: tools.IntPtr(0), + Retention: ptr.To(0), GeoLocation: "EU", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, { Name: "testNs_2", - Retention: tools.IntPtr(86400), + Retention: ptr.To(86400), GeoLocation: "JP", - GroupID: tools.IntPtr(123), + GroupID: ptr.To(123), }, { Name: "testNs_3", - Retention: tools.IntPtr(315360000), + Retention: ptr.To(315360000), GeoLocation: "US", - GroupID: tools.IntPtr(234), + GroupID: ptr.To(234), }, }, }, @@ -203,9 +203,9 @@ func TestGetNamespace(t *testing.T) { }`, expectedResult: &Namespace{ Name: "testNs", - Retention: tools.IntPtr(0), + Retention: ptr.To(0), GeoLocation: "EU", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, "200 OK - staging": { @@ -223,9 +223,9 @@ func TestGetNamespace(t *testing.T) { }`, expectedResult: &Namespace{ Name: "testNs", - Retention: tools.IntPtr(86400), + Retention: ptr.To(86400), GeoLocation: "US", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, "400 bad request - namespace does not exist": { @@ -317,9 +317,9 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceProductionNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(0), + Retention: ptr.To(0), GeoLocation: "EU", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, expectedPath: "/edgekv/v1/networks/production/namespaces", @@ -332,9 +332,9 @@ func TestCreateNamespace(t *testing.T) { }`, expectedResult: &Namespace{ Name: "testNs", - Retention: tools.IntPtr(0), + Retention: ptr.To(0), GeoLocation: "EU", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, "200 OK - staging": { @@ -342,8 +342,8 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(86400), - GroupID: tools.IntPtr(123), + Retention: ptr.To(86400), + GroupID: ptr.To(123), }, }, expectedPath: "/edgekv/v1/networks/staging/namespaces", @@ -356,9 +356,9 @@ func TestCreateNamespace(t *testing.T) { }`, expectedResult: &Namespace{ Name: "testNs", - Retention: tools.IntPtr(86400), + Retention: ptr.To(86400), GeoLocation: "US", - GroupID: tools.IntPtr(123), + GroupID: ptr.To(123), }, }, "400 bad request - invalid geoLocation for staging network": { @@ -366,9 +366,9 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(0), + Retention: ptr.To(0), GeoLocation: "JP", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -404,9 +404,9 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceProductionNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(0), + Retention: ptr.To(0), GeoLocation: "INVALID", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -442,8 +442,8 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(0), - GroupID: tools.IntPtr(0), + Retention: ptr.To(0), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -489,8 +489,8 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(86399), - GroupID: tools.IntPtr(0), + Retention: ptr.To(86399), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -503,8 +503,8 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "testNs", - Retention: tools.IntPtr(315360001), - GroupID: tools.IntPtr(0), + Retention: ptr.To(315360001), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -517,8 +517,8 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "namespaceNameThatHasMoreThan32Letters", - Retention: tools.IntPtr(0), - GroupID: tools.IntPtr(0), + Retention: ptr.To(0), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -531,8 +531,8 @@ func TestCreateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, Namespace: Namespace{ Name: "groupIDLessThan0", - Retention: tools.IntPtr(0), - GroupID: tools.IntPtr(-1), + Retention: ptr.To(0), + GroupID: ptr.To(-1), }, }, withError: func(t *testing.T, err error) { @@ -578,8 +578,8 @@ func TestUpdateNamespace(t *testing.T) { Network: NamespaceProductionNetwork, UpdateNamespace: UpdateNamespace{ Name: "testNs", - Retention: tools.IntPtr(86400), - GroupID: tools.IntPtr(0), + Retention: ptr.To(86400), + GroupID: ptr.To(0), }, }, expectedPath: "/edgekv/v1/networks/production/namespaces/testNs", @@ -592,9 +592,9 @@ func TestUpdateNamespace(t *testing.T) { }`, expectedResult: &Namespace{ Name: "testNs", - Retention: tools.IntPtr(86400), + Retention: ptr.To(86400), GeoLocation: "EU", - GroupID: tools.IntPtr(0), + GroupID: ptr.To(0), }, }, "200 OK - staging": { @@ -602,8 +602,8 @@ func TestUpdateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, UpdateNamespace: UpdateNamespace{ Name: "testNs", - Retention: tools.IntPtr(86400), - GroupID: tools.IntPtr(123), + Retention: ptr.To(86400), + GroupID: ptr.To(123), }, }, expectedPath: "/edgekv/v1/networks/staging/namespaces/testNs", @@ -616,9 +616,9 @@ func TestUpdateNamespace(t *testing.T) { }`, expectedResult: &Namespace{ Name: "testNs", - Retention: tools.IntPtr(86400), + Retention: ptr.To(86400), GeoLocation: "US", - GroupID: tools.IntPtr(123), + GroupID: ptr.To(123), }, }, "409 conflict": { @@ -626,8 +626,8 @@ func TestUpdateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, UpdateNamespace: UpdateNamespace{ Name: "testNs_2", - Retention: tools.IntPtr(0), - GroupID: tools.IntPtr(0), + Retention: ptr.To(0), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -673,8 +673,8 @@ func TestUpdateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, UpdateNamespace: UpdateNamespace{ Name: "namespaceNameThatHasMoreThan32Letters", - Retention: tools.IntPtr(0), - GroupID: tools.IntPtr(0), + Retention: ptr.To(0), + GroupID: ptr.To(0), }, }, withError: func(t *testing.T, err error) { @@ -687,8 +687,8 @@ func TestUpdateNamespace(t *testing.T) { Network: NamespaceStagingNetwork, UpdateNamespace: UpdateNamespace{ Name: "groupIDLessThan0", - Retention: tools.IntPtr(0), - GroupID: tools.IntPtr(-1), + Retention: ptr.To(0), + GroupID: ptr.To(-1), }, }, withError: func(t *testing.T, err error) { diff --git a/pkg/edgeworkers/edgeworker_id.go b/pkg/edgeworkers/edgeworker_id.go index d6a6fa6b..54be07df 100644 --- a/pkg/edgeworkers/edgeworker_id.go +++ b/pkg/edgeworkers/edgeworker_id.go @@ -11,39 +11,6 @@ import ( ) type ( - // EdgeWorkerIDs is EdgeWorker ID API interface - EdgeWorkerIDs interface { - // GetEdgeWorkerID gets details for a specific EdgeWorkerID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-id - GetEdgeWorkerID(context.Context, GetEdgeWorkerIDRequest) (*EdgeWorkerID, error) - - // ListEdgeWorkersID lists EdgeWorkerIDs in the identified group - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-ids - ListEdgeWorkersID(context.Context, ListEdgeWorkersIDRequest) (*ListEdgeWorkersIDResponse, error) - - // CreateEdgeWorkerID creates a new EdgeWorkerID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-ids - CreateEdgeWorkerID(context.Context, CreateEdgeWorkerIDRequest) (*EdgeWorkerID, error) - - // UpdateEdgeWorkerID updates an EdgeWorkerID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/put-id - UpdateEdgeWorkerID(context.Context, UpdateEdgeWorkerIDRequest) (*EdgeWorkerID, error) - - // CloneEdgeWorkerID clones an EdgeWorkerID to change the resource tier - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-id-clone - CloneEdgeWorkerID(context.Context, CloneEdgeWorkerIDRequest) (*EdgeWorkerID, error) - - // DeleteEdgeWorkerID deletes an EdgeWorkerID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/delete-id - DeleteEdgeWorkerID(context.Context, DeleteEdgeWorkerIDRequest) error - } - // GetEdgeWorkerIDRequest contains parameters used to get an EdgeWorkerID GetEdgeWorkerIDRequest struct { EdgeWorkerID int @@ -86,8 +53,8 @@ type ( ResourceTierID int `json:"resourceTierId"` } - // EdgeWorkerIDBodyRequest contains body parameters used to update or clone EdgeWorkerID - EdgeWorkerIDBodyRequest struct { + // EdgeWorkerIDRequestBody contains body parameters used to update or clone EdgeWorkerID + EdgeWorkerIDRequestBody struct { Name string `json:"name"` GroupID int `json:"groupId"` ResourceTierID int `json:"resourceTierId"` @@ -95,13 +62,13 @@ type ( // UpdateEdgeWorkerIDRequest contains body and path parameters used to update EdgeWorkerID UpdateEdgeWorkerIDRequest struct { - EdgeWorkerIDBodyRequest + Body EdgeWorkerIDRequestBody EdgeWorkerID int } // CloneEdgeWorkerIDRequest contains body and path parameters used to clone EdgeWorkerID CloneEdgeWorkerIDRequest struct { - EdgeWorkerIDBodyRequest + Body EdgeWorkerIDRequestBody EdgeWorkerID int } ) @@ -125,9 +92,9 @@ func (c CreateEdgeWorkerIDRequest) Validate() error { // Validate validates CreateEdgeWorkerIDRequest func (c UpdateEdgeWorkerIDRequest) Validate() error { return validation.Errors{ - "Name": validation.Validate(c.EdgeWorkerIDBodyRequest.Name, validation.Required), - "GroupID": validation.Validate(c.EdgeWorkerIDBodyRequest.GroupID, validation.Required), - "ResourceTierID": validation.Validate(c.EdgeWorkerIDBodyRequest.ResourceTierID, validation.Required), + "Name": validation.Validate(c.Body.Name, validation.Required), + "GroupID": validation.Validate(c.Body.GroupID, validation.Required), + "ResourceTierID": validation.Validate(c.Body.ResourceTierID, validation.Required), "EdgeWorkerID": validation.Validate(c.EdgeWorkerID, validation.Required), }.Filter() } @@ -135,9 +102,9 @@ func (c UpdateEdgeWorkerIDRequest) Validate() error { // Validate validates CloneEdgeWorkerIDRequest func (c CloneEdgeWorkerIDRequest) Validate() error { return validation.Errors{ - "Name": validation.Validate(c.EdgeWorkerIDBodyRequest.Name, validation.Required), - "GroupID": validation.Validate(c.EdgeWorkerIDBodyRequest.GroupID, validation.Required), - "ResourceTierID": validation.Validate(c.EdgeWorkerIDBodyRequest.ResourceTierID, validation.Required), + "Name": validation.Validate(c.Body.Name, validation.Required), + "GroupID": validation.Validate(c.Body.GroupID, validation.Required), + "ResourceTierID": validation.Validate(c.Body.ResourceTierID, validation.Required), "EdgeWorkerID": validation.Validate(c.EdgeWorkerID, validation.Required), }.Filter() } @@ -277,7 +244,7 @@ func (e *edgeworkers) UpdateEdgeWorkerID(ctx context.Context, params UpdateEdgeW } var result EdgeWorkerID - resp, err := e.Exec(req, &result, params.EdgeWorkerIDBodyRequest) + resp, err := e.Exec(req, &result, params.Body) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateEdgeWorkerID, err) } @@ -308,7 +275,7 @@ func (e *edgeworkers) CloneEdgeWorkerID(ctx context.Context, params CloneEdgeWor } var result EdgeWorkerID - resp, err := e.Exec(req, &result, params.EdgeWorkerIDBodyRequest) + resp, err := e.Exec(req, &result, params.Body) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrCloneEdgeWorkerID, err) } diff --git a/pkg/edgeworkers/edgeworker_id_test.go b/pkg/edgeworkers/edgeworker_id_test.go index 282e2069..92185fe0 100644 --- a/pkg/edgeworkers/edgeworker_id_test.go +++ b/pkg/edgeworkers/edgeworker_id_test.go @@ -547,7 +547,7 @@ func TestUpdateEdgeWorkerID(t *testing.T) { }{ "200 OK - update EdgeWorkerID": { params: UpdateEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Update EdgeWorkerID", ResourceTierID: 123, @@ -583,14 +583,14 @@ func TestUpdateEdgeWorkerID(t *testing.T) { }, "validation error - empty body parameters": { params: UpdateEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{}, - EdgeWorkerID: 54321, + Body: EdgeWorkerIDRequestBody{}, + EdgeWorkerID: 54321, }, withError: ErrStructValidation, }, "validation error - empty edgeworker id": { params: UpdateEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Update EdgeWorkerID", ResourceTierID: 123, @@ -600,7 +600,7 @@ func TestUpdateEdgeWorkerID(t *testing.T) { }, "500 internal server error": { params: UpdateEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Update EdgeWorkerID", ResourceTierID: 123, @@ -635,7 +635,7 @@ func TestUpdateEdgeWorkerID(t *testing.T) { }, "403 Forbidden - incorrect credentials": { params: UpdateEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Update EdgeWorkerID", ResourceTierID: 123, @@ -712,7 +712,7 @@ func TestCloneEdgeWorkerID(t *testing.T) { }{ "200 OK - clone EdgeWorkerID with different resourceTierId": { params: CloneEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Clone EdgeWorkerID", ResourceTierID: 123, @@ -754,7 +754,7 @@ func TestCloneEdgeWorkerID(t *testing.T) { }, "validation error - empty edgeworker id": { params: CloneEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Update EdgeWorkerID", ResourceTierID: 123, @@ -764,7 +764,7 @@ func TestCloneEdgeWorkerID(t *testing.T) { }, "500 internal server error": { params: CloneEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Clone EdgeWorkerID", ResourceTierID: 123, @@ -799,7 +799,7 @@ func TestCloneEdgeWorkerID(t *testing.T) { }, "403 Forbidden - incorrect credentials": { params: CloneEdgeWorkerIDRequest{ - EdgeWorkerIDBodyRequest: EdgeWorkerIDBodyRequest{ + Body: EdgeWorkerIDRequestBody{ GroupID: 12345, Name: "Update EdgeWorkerID", ResourceTierID: 123, diff --git a/pkg/edgeworkers/edgeworker_version.go b/pkg/edgeworkers/edgeworker_version.go index 11ec997c..9b0502f3 100644 --- a/pkg/edgeworkers/edgeworker_version.go +++ b/pkg/edgeworkers/edgeworker_version.go @@ -13,34 +13,6 @@ import ( ) type ( - // EdgeWorkerVersions is EdgeWorker Version API interface - EdgeWorkerVersions interface { - // GetEdgeWorkerVersion gets details for a specific EdgeWorkerVersion - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-version - GetEdgeWorkerVersion(context.Context, GetEdgeWorkerVersionRequest) (*EdgeWorkerVersion, error) - - // ListEdgeWorkerVersions lists EdgeWorkerVersions in the identified group - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-versions - ListEdgeWorkerVersions(context.Context, ListEdgeWorkerVersionsRequest) (*ListEdgeWorkerVersionsResponse, error) - - // GetEdgeWorkerVersionContent gets content bundle for a specific EdgeWorkerVersion - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-version-content - GetEdgeWorkerVersionContent(context.Context, GetEdgeWorkerVersionContentRequest) (*Bundle, error) - - // CreateEdgeWorkerVersion creates a new EdgeWorkerVersion - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-versions - CreateEdgeWorkerVersion(context.Context, CreateEdgeWorkerVersionRequest) (*EdgeWorkerVersion, error) - - // DeleteEdgeWorkerVersion deletes an EdgeWorkerVersion - // - // See: https://techdocs.akamai.com/edgeworkers/reference/delete-version - DeleteEdgeWorkerVersion(context.Context, DeleteEdgeWorkerVersionRequest) error - } - // GetEdgeWorkerVersionRequest contains parameters used to get an EdgeWorkerVersion GetEdgeWorkerVersionRequest EdgeWorkerVersionRequest diff --git a/pkg/edgeworkers/edgeworkers.go b/pkg/edgeworkers/edgeworkers.go index 2d643ac8..003d2317 100644 --- a/pkg/edgeworkers/edgeworkers.go +++ b/pkg/edgeworkers/edgeworkers.go @@ -2,9 +2,10 @@ package edgeworkers import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,22 +16,257 @@ var ( type ( // Edgeworkers is the api interface for EdgeWorkers and EdgeKV Edgeworkers interface { - Activations - Contracts - Deactivations - EdgeKVAccessTokens - EdgeKVInitialize - EdgeKVItems - EdgeKVNamespaces - EdgeWorkerIDs - EdgeWorkerVersions - Groups - PermissionGroups - Properties - Reports - ResourceTiers - SecureTokens - Validations + // Activations + + // ListActivations lists all activations for an EdgeWorker + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-activations-1 + ListActivations(context.Context, ListActivationsRequest) (*ListActivationsResponse, error) + + // GetActivation fetches an EdgeWorker activation by id + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-activation-1 + GetActivation(context.Context, GetActivationRequest) (*Activation, error) + + // ActivateVersion activates an EdgeWorker version on a given network + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-activations-1 + ActivateVersion(context.Context, ActivateVersionRequest) (*Activation, error) + + // CancelPendingActivation cancels pending activation with a given id + // + // See: https://techdocs.akamai.com/edgeworkers/reference/cancel-activation + CancelPendingActivation(context.Context, CancelActivationRequest) (*Activation, error) + + // Contracts + + // ListContracts lists contract IDs that can be used to list resource tiers + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-contracts-1 + ListContracts(context.Context) (*ListContractsResponse, error) + + // Deactivations + + // ListDeactivations lists all deactivations for a given EdgeWorker ID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-deactivations-1 + ListDeactivations(context.Context, ListDeactivationsRequest) (*ListDeactivationsResponse, error) + + // GetDeactivation gets details for a specific deactivation + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-deactivation-1 + GetDeactivation(context.Context, GetDeactivationRequest) (*Deactivation, error) + + // DeactivateVersion deactivates an existing EdgeWorker version on the akamai network + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-deactivations-1 + DeactivateVersion(context.Context, DeactivateVersionRequest) (*Deactivation, error) + + // EdgeKVAccessTokens + + // CreateEdgeKVAccessToken generates EdgeKV specific access token + // + // See: https://techdocs.akamai.com/edgekv/reference/post-tokens + CreateEdgeKVAccessToken(context.Context, CreateEdgeKVAccessTokenRequest) (*CreateEdgeKVAccessTokenResponse, error) + + // GetEdgeKVAccessToken retrieves an EdgeKV access token + // + // See: https://techdocs.akamai.com/edgekv/reference/get-token + GetEdgeKVAccessToken(context.Context, GetEdgeKVAccessTokenRequest) (*GetEdgeKVAccessTokenResponse, error) + + // ListEdgeKVAccessTokens lists EdgeKV access tokens + // + // See: https://techdocs.akamai.com/edgekv/reference/get-tokens + ListEdgeKVAccessTokens(context.Context, ListEdgeKVAccessTokensRequest) (*ListEdgeKVAccessTokensResponse, error) + + // DeleteEdgeKVAccessToken revokes an EdgeKV access token + // + // See: https://techdocs.akamai.com/edgekv/reference/delete-token + DeleteEdgeKVAccessToken(context.Context, DeleteEdgeKVAccessTokenRequest) (*DeleteEdgeKVAccessTokenResponse, error) + + // EdgeKVInitialize + + // InitializeEdgeKV Initialize the EdgeKV database + // + // See: https://techdocs.akamai.com/edgekv/reference/put-initialize + InitializeEdgeKV(ctx context.Context) (*EdgeKVInitializationStatus, error) + + // GetEdgeKVInitializationStatus is used to check on the current initialization status + // + // See: https://techdocs.akamai.com/edgekv/reference/get-initialize + GetEdgeKVInitializationStatus(ctx context.Context) (*EdgeKVInitializationStatus, error) + + // EdgeKVItems + + // ListItems lists items in EdgeKV group + // + // See: https://techdocs.akamai.com/edgekv/reference/get-group-1 + ListItems(context.Context, ListItemsRequest) (*ListItemsResponse, error) + + // GetItem reads an item from EdgeKV group + // + // See: https://techdocs.akamai.com/edgekv/reference/get-item + GetItem(context.Context, GetItemRequest) (*Item, error) + + // UpsertItem creates or updates an item in EdgeKV group + // + // See: https://techdocs.akamai.com/edgekv/reference/put-item + UpsertItem(context.Context, UpsertItemRequest) (*string, error) + + // DeleteItem deletes an item from EdgeKV group + // + // See: https://techdocs.akamai.com/edgekv/reference/delete-item + DeleteItem(context.Context, DeleteItemRequest) (*string, error) + + // EdgeKVNamespaces + + // ListEdgeKVNamespaces lists all namespaces in the given network + // + // See: https://techdocs.akamai.com/edgekv/reference/get-namespaces + ListEdgeKVNamespaces(context.Context, ListEdgeKVNamespacesRequest) (*ListEdgeKVNamespacesResponse, error) + + // GetEdgeKVNamespace fetches a namespace by name + // + // See: https://techdocs.akamai.com/edgekv/reference/get-namespace + GetEdgeKVNamespace(context.Context, GetEdgeKVNamespaceRequest) (*Namespace, error) + + // CreateEdgeKVNamespace creates a namespace on the given network + // + // See: https://techdocs.akamai.com/edgekv/reference/post-namespace + CreateEdgeKVNamespace(context.Context, CreateEdgeKVNamespaceRequest) (*Namespace, error) + + // UpdateEdgeKVNamespace updates a namespace + // + // See: https://techdocs.akamai.com/edgekv/reference/put-namespace + UpdateEdgeKVNamespace(context.Context, UpdateEdgeKVNamespaceRequest) (*Namespace, error) + + // EdgeWorkerIDs + + // GetEdgeWorkerID gets details for a specific EdgeWorkerID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-id + GetEdgeWorkerID(context.Context, GetEdgeWorkerIDRequest) (*EdgeWorkerID, error) + + // ListEdgeWorkersID lists EdgeWorkerIDs in the identified group + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-ids + ListEdgeWorkersID(context.Context, ListEdgeWorkersIDRequest) (*ListEdgeWorkersIDResponse, error) + + // CreateEdgeWorkerID creates a new EdgeWorkerID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-ids + CreateEdgeWorkerID(context.Context, CreateEdgeWorkerIDRequest) (*EdgeWorkerID, error) + + // UpdateEdgeWorkerID updates an EdgeWorkerID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/put-id + UpdateEdgeWorkerID(context.Context, UpdateEdgeWorkerIDRequest) (*EdgeWorkerID, error) + + // CloneEdgeWorkerID clones an EdgeWorkerID to change the resource tier + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-id-clone + CloneEdgeWorkerID(context.Context, CloneEdgeWorkerIDRequest) (*EdgeWorkerID, error) + + // DeleteEdgeWorkerID deletes an EdgeWorkerID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/delete-id + DeleteEdgeWorkerID(context.Context, DeleteEdgeWorkerIDRequest) error + + // EdgeWorkerVersions + + // GetEdgeWorkerVersion gets details for a specific EdgeWorkerVersion + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-version + GetEdgeWorkerVersion(context.Context, GetEdgeWorkerVersionRequest) (*EdgeWorkerVersion, error) + + // ListEdgeWorkerVersions lists EdgeWorkerVersions in the identified group + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-versions + ListEdgeWorkerVersions(context.Context, ListEdgeWorkerVersionsRequest) (*ListEdgeWorkerVersionsResponse, error) + + // GetEdgeWorkerVersionContent gets content bundle for a specific EdgeWorkerVersion + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-version-content + GetEdgeWorkerVersionContent(context.Context, GetEdgeWorkerVersionContentRequest) (*Bundle, error) + + // CreateEdgeWorkerVersion creates a new EdgeWorkerVersion + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-versions + CreateEdgeWorkerVersion(context.Context, CreateEdgeWorkerVersionRequest) (*EdgeWorkerVersion, error) + + // DeleteEdgeWorkerVersion deletes an EdgeWorkerVersion + // + // See: https://techdocs.akamai.com/edgeworkers/reference/delete-version + DeleteEdgeWorkerVersion(context.Context, DeleteEdgeWorkerVersionRequest) error + + // Groups + + // ListGroupsWithinNamespace lists group identifiers created when writing items to a namespace + // + // See: https://techdocs.akamai.com/edgekv/reference/get-groups + ListGroupsWithinNamespace(context.Context, ListGroupsWithinNamespaceRequest) ([]string, error) + + // PermissionGroups + + // GetPermissionGroup gets details on the capabilities enabled within a specified group + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-group + GetPermissionGroup(context.Context, GetPermissionGroupRequest) (*PermissionGroup, error) + + // ListPermissionGroups lists groups and the associated permission capabilities + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-groups + ListPermissionGroups(context.Context) (*ListPermissionGroupsResponse, error) + + // Properties + + // ListProperties lists all properties for a given edgeworker ID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-properties + ListProperties(context.Context, ListPropertiesRequest) (*ListPropertiesResponse, error) + + // Reports + + // GetSummaryReport gets summary overview for EdgeWorker reports. Report id is 1 + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-report + GetSummaryReport(context.Context, GetSummaryReportRequest) (*GetSummaryReportResponse, error) + + // GetReport gets details for an EdgeWorker + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-report + GetReport(context.Context, GetReportRequest) (*GetReportResponse, error) + + // ListReports lists EdgeWorker reports + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-reports + ListReports(context.Context) (*ListReportsResponse, error) + + // ResourceTiers + + // ListResourceTiers lists all resource tiers for a given contract + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-resource-tiers + ListResourceTiers(context.Context, ListResourceTiersRequest) (*ListResourceTiersResponse, error) + + // GetResourceTier returns resource tier for a given edgeworker ID + // + // See: https://techdocs.akamai.com/edgeworkers/reference/get-id-resource-tier + GetResourceTier(context.Context, GetResourceTierRequest) (*ResourceTier, error) + + // SecureTokens + + // CreateSecureToken creates a new secure token + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-secure-token + CreateSecureToken(context.Context, CreateSecureTokenRequest) (*CreateSecureTokenResponse, error) + + // Validations + + // ValidateBundle given bundle validates it and returns a list of errors and/or warnings + // + // See: https://techdocs.akamai.com/edgeworkers/reference/post-validations + ValidateBundle(context.Context, ValidateBundleRequest) (*ValidateBundleResponse, error) } edgeworkers struct { diff --git a/pkg/edgeworkers/edgeworkers_test.go b/pkg/edgeworkers/edgeworkers_test.go index 3f26db9c..7801cf0c 100644 --- a/pkg/edgeworkers/edgeworkers_test.go +++ b/pkg/edgeworkers/edgeworkers_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/edgeworkers/errors.go b/pkg/edgeworkers/errors.go index b26e913e..cb39ddbf 100644 --- a/pkg/edgeworkers/errors.go +++ b/pkg/edgeworkers/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/edgeworkers/errors_test.go b/pkg/edgeworkers/errors_test.go index c39e3272..5da22969 100644 --- a/pkg/edgeworkers/errors_test.go +++ b/pkg/edgeworkers/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/edgeworkers/permission_group.go b/pkg/edgeworkers/permission_group.go index 10f70a68..f91404d6 100644 --- a/pkg/edgeworkers/permission_group.go +++ b/pkg/edgeworkers/permission_group.go @@ -10,19 +10,6 @@ import ( ) type ( - // PermissionGroups is an edgeworkers permission groups API interface - PermissionGroups interface { - // GetPermissionGroup gets details on the capabilities enabled within a specified group - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-group - GetPermissionGroup(context.Context, GetPermissionGroupRequest) (*PermissionGroup, error) - - // ListPermissionGroups lists groups and the associated permission capabilities - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-groups - ListPermissionGroups(context.Context) (*ListPermissionGroupsResponse, error) - } - // GetPermissionGroupRequest contains parameters used to get a permission group GetPermissionGroupRequest struct { GroupID string diff --git a/pkg/edgeworkers/properties.go b/pkg/edgeworkers/properties.go index be939ecf..bb2236f5 100644 --- a/pkg/edgeworkers/properties.go +++ b/pkg/edgeworkers/properties.go @@ -12,14 +12,6 @@ import ( ) type ( - // Properties is an edgeworkers properties API interface - Properties interface { - // ListProperties lists all properties for a given edgeworker ID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-properties - ListProperties(context.Context, ListPropertiesRequest) (*ListPropertiesResponse, error) - } - // ListPropertiesRequest contains parameters used to list properties ListPropertiesRequest struct { EdgeWorkerID int diff --git a/pkg/edgeworkers/report.go b/pkg/edgeworkers/report.go index 1a71035e..3f21a21b 100644 --- a/pkg/edgeworkers/report.go +++ b/pkg/edgeworkers/report.go @@ -11,24 +11,6 @@ import ( ) type ( - // Reports is an edgeworkers reports API interface - Reports interface { - // GetSummaryReport gets summary overview for EdgeWorker reports. Report id is 1 - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-report - GetSummaryReport(context.Context, GetSummaryReportRequest) (*GetSummaryReportResponse, error) - - // GetReport gets details for an EdgeWorker - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-report - GetReport(context.Context, GetReportRequest) (*GetReportResponse, error) - - // ListReports lists EdgeWorker reports - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-reports - ListReports(context.Context) (*ListReportsResponse, error) - } - // GetSummaryReportRequest contains parameters used to get summary overview for EdgeWorker reports GetSummaryReportRequest struct { Start string diff --git a/pkg/edgeworkers/report_test.go b/pkg/edgeworkers/report_test.go index 4ca380cc..d8203759 100644 --- a/pkg/edgeworkers/report_test.go +++ b/pkg/edgeworkers/report_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,7 +26,7 @@ func TestGetSummaryReport(t *testing.T) { Start: "2022-01-10T03:00:00Z", End: "2022-01-14T13:22:28Z", EdgeWorker: "1234", - Status: tools.StringPtr(StatusSuccess), + Status: ptr.To(StatusSuccess), }, expectedPath: "/edgeworkers/v1/reports/1?edgeWorker=1234&end=2022-01-14T13%3A22%3A28Z&start=2022-01-10T03%3A00%3A00Z&status=success", responseBody: ` @@ -101,7 +101,7 @@ func TestGetSummaryReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - Status: tools.StringPtr("not_valid"), + Status: ptr.To("not_valid"), }, withError: ErrStructValidation, }, @@ -110,7 +110,7 @@ func TestGetSummaryReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - Status: tools.StringPtr(""), + Status: ptr.To(""), }, expectedPath: "/edgeworkers/v1/reports/1?edgeWorker=37017&end=2022-01-01T00%3A00%3A00Z&start=2021-12-04T00%3A00%3A00Z", withError: ErrStructValidation, @@ -120,7 +120,7 @@ func TestGetSummaryReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - EventHandler: tools.StringPtr("not_valid"), + EventHandler: ptr.To("not_valid"), }, withError: ErrStructValidation, }, @@ -129,7 +129,7 @@ func TestGetSummaryReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - EventHandler: tools.StringPtr(""), + EventHandler: ptr.To(""), }, expectedPath: "/edgeworkers/v1/reports/1?edgeWorker=37017&end=2022-01-01T00%3A00%3A00Z&start=2021-12-04T00%3A00%3A00Z", withError: ErrStructValidation, @@ -644,13 +644,13 @@ func TestGetReport(t *testing.T) { StartDateTime: "2022-01-10T03:45:00Z", EdgeWorkerVersion: "10.18", Invocations: 1, - Status: tools.StringPtr("unimplementedEventHandler"), + Status: ptr.To("unimplementedEventHandler"), }, { StartDateTime: "2022-01-10T06:00:00Z", EdgeWorkerVersion: "10.18", Invocations: 1, - Status: tools.StringPtr("unimplementedEventHandler"), + Status: ptr.To("unimplementedEventHandler"), }, }, OnOriginRequest: &OnOriginRequest{ @@ -658,13 +658,13 @@ func TestGetReport(t *testing.T) { StartDateTime: "2022-01-10T03:45:00Z", EdgeWorkerVersion: "10.18", Invocations: 1, - Status: tools.StringPtr("unimplementedEventHandler"), + Status: ptr.To("unimplementedEventHandler"), }, { StartDateTime: "2022-01-10T06:00:00Z", EdgeWorkerVersion: "10.18", Invocations: 1, - Status: tools.StringPtr("unimplementedEventHandler"), + Status: ptr.To("unimplementedEventHandler"), }, }, OnClientResponse: &OnClientResponse{ @@ -672,13 +672,13 @@ func TestGetReport(t *testing.T) { StartDateTime: "2022-01-10T03:00:00Z", EdgeWorkerVersion: "10.18", Invocations: 8, - Status: tools.StringPtr("success"), + Status: ptr.To("success"), }, { StartDateTime: "2022-01-10T03:05:00Z", EdgeWorkerVersion: "10.18", Invocations: 15, - Status: tools.StringPtr("success"), + Status: ptr.To("success"), }, }, OnOriginResponse: &OnOriginResponse{ @@ -686,13 +686,13 @@ func TestGetReport(t *testing.T) { StartDateTime: "2022-01-10T03:45:00Z", EdgeWorkerVersion: "10.18", Invocations: 1, - Status: tools.StringPtr("unimplementedEventHandler"), + Status: ptr.To("unimplementedEventHandler"), }, { StartDateTime: "2022-01-10T06:00:00Z", EdgeWorkerVersion: "10.18", Invocations: 1, - Status: tools.StringPtr("unimplementedEventHandler"), + Status: ptr.To("unimplementedEventHandler"), }, }, OnClientRequest: &OnClientRequest{ @@ -700,13 +700,13 @@ func TestGetReport(t *testing.T) { StartDateTime: "2022-01-10T03:00:00Z", EdgeWorkerVersion: "10.18", Invocations: 8, - Status: tools.StringPtr("success"), + Status: ptr.To("success"), }, { StartDateTime: "2022-01-10T03:05:00Z", EdgeWorkerVersion: "10.18", Invocations: 15, - Status: tools.StringPtr("success"), + Status: ptr.To("success"), }, }, }, @@ -979,8 +979,8 @@ func TestGetReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - Status: tools.StringPtr(StatusSuccess), - EventHandler: tools.StringPtr(EventHandlerOnClientRequest), + Status: ptr.To(StatusSuccess), + EventHandler: ptr.To(EventHandlerOnClientRequest), }, responseStatus: http.StatusOK, expectedPath: "/edgeworkers/v1/reports/4?edgeWorker=37017&end=2022-01-01T00%3A00%3A00Z&eventHandler=onClientRequest&start=2021-12-04T00%3A00%3A00Z&status=success", @@ -1011,7 +1011,7 @@ func TestGetReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - Status: tools.StringPtr("not_valid"), + Status: ptr.To("not_valid"), }, withError: ErrStructValidation, }, @@ -1021,7 +1021,7 @@ func TestGetReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - Status: tools.StringPtr(""), + Status: ptr.To(""), }, expectedPath: "/edgeworkers/v1/reports/4?edgeWorker=37017&end=2022-01-01T00%3A00%3A00Z&start=2021-12-04T00%3A00%3A00Z", withError: ErrStructValidation, @@ -1032,7 +1032,7 @@ func TestGetReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - EventHandler: tools.StringPtr("not_valid"), + EventHandler: ptr.To("not_valid"), }, withError: ErrStructValidation, }, @@ -1042,7 +1042,7 @@ func TestGetReport(t *testing.T) { Start: "2021-12-04T00:00:00Z", End: "2022-01-01T00:00:00Z", EdgeWorker: "37017", - EventHandler: tools.StringPtr(""), + EventHandler: ptr.To(""), }, expectedPath: "/edgeworkers/v1/reports/4?edgeWorker=37017&end=2022-01-01T00%3A00%3A00Z&start=2021-12-04T00%3A00%3A00Z", withError: ErrStructValidation, diff --git a/pkg/edgeworkers/resource_tier.go b/pkg/edgeworkers/resource_tier.go index 204f3cf3..053c2dd2 100644 --- a/pkg/edgeworkers/resource_tier.go +++ b/pkg/edgeworkers/resource_tier.go @@ -11,19 +11,6 @@ import ( ) type ( - // ResourceTiers is an edgeworkers resource tiers API interface - ResourceTiers interface { - // ListResourceTiers lists all resource tiers for a given contract - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-resource-tiers - ListResourceTiers(context.Context, ListResourceTiersRequest) (*ListResourceTiersResponse, error) - - // GetResourceTier returns resource tier for a given edgeworker ID - // - // See: https://techdocs.akamai.com/edgeworkers/reference/get-id-resource-tier - GetResourceTier(context.Context, GetResourceTierRequest) (*ResourceTier, error) - } - // ListResourceTiersRequest contains parameters used to list resource tiers ListResourceTiersRequest struct { ContractID string diff --git a/pkg/edgeworkers/secure_tokens.go b/pkg/edgeworkers/secure_tokens.go index ee816b89..97af4d54 100644 --- a/pkg/edgeworkers/secure_tokens.go +++ b/pkg/edgeworkers/secure_tokens.go @@ -10,14 +10,6 @@ import ( ) type ( - // SecureTokens is EdgeWorker Secure Token API interface - SecureTokens interface { - // CreateSecureToken creates a new secure token - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-secure-token - CreateSecureToken(context.Context, CreateSecureTokenRequest) (*CreateSecureTokenResponse, error) - } - // CreateSecureTokenRequest represents parameters for CreateSecureToken CreateSecureTokenRequest struct { ACL string `json:"acl,omitempty"` diff --git a/pkg/edgeworkers/validations.go b/pkg/edgeworkers/validations.go index 9010eda7..90982aa1 100644 --- a/pkg/edgeworkers/validations.go +++ b/pkg/edgeworkers/validations.go @@ -11,14 +11,6 @@ import ( ) type ( - // Validations is an edgeworkers validations API interface - Validations interface { - // ValidateBundle given bundle validates it and returns a list of errors and/or warnings - // - // See: https://techdocs.akamai.com/edgeworkers/reference/post-validations - ValidateBundle(context.Context, ValidateBundleRequest) (*ValidateBundleResponse, error) - } - // ValidateBundleRequest contains request bundle parameter to validate ValidateBundleRequest struct { Bundle diff --git a/pkg/gtm/asmap.go b/pkg/gtm/asmap.go index 0a98c254..a110469e 100644 --- a/pkg/gtm/asmap.go +++ b/pkg/gtm/asmap.go @@ -2,34 +2,15 @@ package gtm import ( "context" + "errors" "fmt" "net/http" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) -// ASMaps contains operations available on a ASmap resource. type ( - ASMaps interface { - // ListASMaps retrieves all AsMaps. - // - // See: https://techdocs.akamai.com/gtm/reference/get-as-maps - ListASMaps(context.Context, string) ([]*ASMap, error) - // GetASMap retrieves a AsMap with the given name. - // - // See: https://techdocs.akamai.com/gtm/reference/get-as-map - GetASMap(context.Context, string, string) (*ASMap, error) - // CreateASMap creates the datacenter identified by the receiver argument in the specified domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-as-map - CreateASMap(context.Context, *ASMap, string) (*ASMapResponse, error) - // DeleteASMap deletes the datacenter identified by the receiver argument from the domain specified. - // - // See: https://techdocs.akamai.com/gtm/reference/delete-as-map - DeleteASMap(context.Context, *ASMap, string) (*ResponseStatus, error) - // UpdateASMap updates the datacenter identified in the receiver argument in the provided domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-as-map - UpdateASMap(context.Context, *ASMap, string) (*ResponseStatus, error) - } // ASAssignment represents a GTM as map assignment structure ASAssignment struct { DatacenterBase @@ -39,34 +20,137 @@ type ( // ASMap represents a GTM ASMap ASMap struct { DefaultDatacenter *DatacenterBase `json:"defaultDatacenter"` - Assignments []*ASAssignment `json:"assignments,omitempty"` + Assignments []ASAssignment `json:"assignments,omitempty"` Name string `json:"name"` - Links []*Link `json:"links,omitempty"` + Links []Link `json:"links,omitempty"` } // ASMapList represents the returned GTM ASMap List body ASMapList struct { - ASMapItems []*ASMap `json:"items"` + ASMapItems []ASMap `json:"items"` } -) -// Validate validates ASMap -func (a *ASMap) Validate() error { - if len(a.Name) < 1 { - return fmt.Errorf("ASMap is missing Name") + // ListASMapsRequest contains request parameters for ListASMaps + ListASMapsRequest struct { + DomainName string + } + + // GetASMapRequest contains request parameters for GetASMap + GetASMapRequest struct { + ASMapName string + DomainName string + } + + // GetASMapResponse contains the response data from GetASMap operation + GetASMapResponse ASMap + + // ASMapRequest contains request parameters + ASMapRequest struct { + ASMap *ASMap + DomainName string + } + + // CreateASMapRequest contains request parameters for CreateASMap + CreateASMapRequest ASMapRequest + + // CreateASMapResponse contains the response data from CreateASMap operation + CreateASMapResponse struct { + Resource *ASMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // UpdateASMapRequest contains request parameters for UpdateASMap + UpdateASMapRequest ASMapRequest + + // UpdateASMapResponse contains the response data from UpdateASMap operation + UpdateASMapResponse struct { + Resource *ASMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // DeleteASMapRequest contains request parameters for DeleteASMap + DeleteASMapRequest struct { + ASMapName string + DomainName string } - if a.DefaultDatacenter == nil { - return fmt.Errorf("ASMap is missing DefaultDatacenter") + + // DeleteASMapResponse contains the response data from DeleteASMap operation + DeleteASMapResponse struct { + Resource *ASMap `json:"resource"` + Status *ResponseStatus `json:"status"` } +) + +var ( + // ErrListASMaps is returned when ListASMaps fails + ErrListASMaps = errors.New("list asmaps") + // ErrGetASMap is returned when GetASMap fails + ErrGetASMap = errors.New("get asmap") + // ErrCreateASMap is returned when CreateASMap fails + ErrCreateASMap = errors.New("create asmap") + // ErrUpdateASMap is returned when UpdateASMap fails + ErrUpdateASMap = errors.New("update asmap") + // ErrDeleteASMap is returned when DeleteASMap fails + ErrDeleteASMap = errors.New("delete asmap") +) - return nil +// Validate validates ListASMapsRequest +func (r ListASMapsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) +} + +// Validate validates GetASMapRequest +func (r GetASMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "ASMapName": validation.Validate(r.ASMapName, validation.Required), + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) +} + +// Validate validates CreateASMapRequest +func (r CreateASMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "ASMap": validation.Validate(r.ASMap, validation.Required), + }) +} + +// Validate validates ASMap +func (a ASMap) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Name": validation.Validate(a.Name, validation.Required), + "DefaultDatacenter": validation.Validate(a.DefaultDatacenter, validation.Required), + "Assignments": validation.Validate(a.Assignments, validation.Required), + }) } -func (g *gtm) ListASMaps(ctx context.Context, domainName string) ([]*ASMap, error) { +// Validate validates UpdateASMapRequest +func (r UpdateASMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "ASMap": validation.Validate(r.ASMap, validation.Required), + }) +} + +// Validate validates DeleteASMapRequest +func (r DeleteASMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "ASMapName": validation.Validate(r.ASMapName, validation.Required), + }) +} + +func (g *gtm) ListASMaps(ctx context.Context, params ListASMapsRequest) ([]ASMap, error) { logger := g.Log(ctx) logger.Debug("ListASMaps") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrListASMaps, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListASMaps request: %w", err) @@ -86,18 +170,22 @@ func (g *gtm) ListASMaps(ctx context.Context, domainName string) ([]*ASMap, erro return result.ASMapItems, nil } -func (g *gtm) GetASMap(ctx context.Context, asMapName, domainName string) (*ASMap, error) { +func (g *gtm) GetASMap(ctx context.Context, params GetASMapRequest) (*GetASMapResponse, error) { logger := g.Log(ctx) logger.Debug("GetASMap") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", domainName, asMapName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetASMap, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", params.DomainName, params.ASMapName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetASMap request: %w", err) } setVersionHeader(req, schemaVersion) - var result ASMap + var result GetASMapResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetASMap request failed: %w", err) @@ -110,66 +198,78 @@ func (g *gtm) GetASMap(ctx context.Context, asMapName, domainName string) (*ASMa return &result, nil } -func (g *gtm) CreateASMap(ctx context.Context, asMap *ASMap, domainName string) (*ASMapResponse, error) { +func (g *gtm) CreateASMap(ctx context.Context, params CreateASMapRequest) (*CreateASMapResponse, error) { logger := g.Log(ctx) logger.Debug("CreateASMap") - return asMap.save(ctx, g, domainName) -} + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateASMap, ErrStructValidation, err) + } -func (g *gtm) UpdateASMap(ctx context.Context, asMap *ASMap, domainName string) (*ResponseStatus, error) { - logger := g.Log(ctx) - logger.Debug("UpdateASMap") + uri := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", params.DomainName, params.ASMap.Name) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, nil) + if err != nil { + return nil, fmt.Errorf("failed to create ASMap request: %w", err) + } + setVersionHeader(req, schemaVersion) - stat, err := asMap.save(ctx, g, domainName) + var result CreateASMapResponse + resp, err := g.Exec(req, &result, params.ASMap) if err != nil { - return nil, err + return nil, fmt.Errorf("ASMap request failed: %w", err) } - return stat.Status, err + + if resp.StatusCode != http.StatusCreated { + return nil, g.Error(resp) + } + + return &result, nil } -// save AsMap in given domain. Common path for Create and Update. -func (a *ASMap) save(ctx context.Context, g *gtm, domainName string) (*ASMapResponse, error) { - if err := a.Validate(); err != nil { - return nil, fmt.Errorf("ASMap validation failed. %w", err) +func (g *gtm) UpdateASMap(ctx context.Context, params UpdateASMapRequest) (*UpdateASMapResponse, error) { + logger := g.Log(ctx) + logger.Debug("UpdateASMap") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateASMap, ErrStructValidation, err) } - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", domainName, a.Name) + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", params.DomainName, params.ASMap.Name) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ASMap request: %w", err) } setVersionHeader(req, schemaVersion) - var result ASMapResponse - resp, err := g.Exec(req, &result, a) + var result UpdateASMapResponse + resp, err := g.Exec(req, &result, params.ASMap) if err != nil { return nil, fmt.Errorf("ASMap request failed: %w", err) } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusOK { return nil, g.Error(resp) } return &result, nil } -func (g *gtm) DeleteASMap(ctx context.Context, asMap *ASMap, domainName string) (*ResponseStatus, error) { +func (g *gtm) DeleteASMap(ctx context.Context, params DeleteASMapRequest) (*DeleteASMapResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteASMap") - if err := asMap.Validate(); err != nil { - return nil, fmt.Errorf("resource validation failed: %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteASMap, ErrStructValidation, err) } - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", domainName, asMap.Name) + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/as-maps/%s", params.DomainName, params.ASMapName) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Delete request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseBody + var result DeleteASMapResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("ASMap request failed: %w", err) @@ -179,5 +279,5 @@ func (g *gtm) DeleteASMap(ctx context.Context, asMap *ASMap, domainName string) return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } diff --git a/pkg/gtm/asmap_test.go b/pkg/gtm/asmap_test.go index 7a322f30..226756c6 100644 --- a/pkg/gtm/asmap_test.go +++ b/pkg/gtm/asmap_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,16 +27,18 @@ func TestGTM_ListASMap(t *testing.T) { } tests := map[string]struct { - domainName string + params ListASMapsRequest responseStatus int responseBody string expectedPath string - expectedResponse []*ASMap + expectedResponse []ASMap withError error headers http.Header }{ "200 OK": { - domainName: "example.akadns.net", + params: ListASMapsRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -46,7 +48,9 @@ func TestGTM_ListASMap(t *testing.T) { expectedResponse: result.ASMapItems, }, "500 internal server error": { - domainName: "example.akadns.net", + params: ListASMapsRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -79,7 +83,7 @@ func TestGTM_ListASMap(t *testing.T) { result, err := client.ListASMaps( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -91,7 +95,7 @@ func TestGTM_ListASMap(t *testing.T) { } func TestGTM_GetASMap(t *testing.T) { - var result ASMap + var result GetASMapResponse respData, err := loadTestData("TestGTM_GetASMap.resp.json") if err != nil { @@ -103,18 +107,19 @@ func TestGTM_GetASMap(t *testing.T) { } tests := map[string]struct { - name string - domainName string + params GetASMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ASMap + expectedResponse *GetASMapResponse withError error headers http.Header }{ "200 OK": { - name: "Software-rollout", - domainName: "example.akadns.net", + params: GetASMapRequest{ + DomainName: "example.akadns.net", + ASMapName: "Software-rollout", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -124,8 +129,10 @@ func TestGTM_GetASMap(t *testing.T) { expectedResponse: &result, }, "500 internal server error": { - name: "Software-rollout", - domainName: "example.akadns.net", + params: GetASMapRequest{ + DomainName: "example.akadns.net", + ASMapName: "Software-rollout", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -158,7 +165,7 @@ func TestGTM_GetASMap(t *testing.T) { result, err := client.GetASMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.name, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -170,7 +177,7 @@ func TestGTM_GetASMap(t *testing.T) { } func TestGTM_CreateASMap(t *testing.T) { - var result ASMapResponse + var result CreateASMapResponse var req ASMap respData, err := loadTestData("TestGTM_CreateASMap.resp.json") @@ -192,29 +199,32 @@ func TestGTM_CreateASMap(t *testing.T) { } tests := map[string]struct { - asMap *ASMap - domainName string + params CreateASMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ASMapResponse + expectedResponse *CreateASMapResponse withError error headers http.Header }{ "200 OK": { - asMap: &req, - domainName: "example.akadns.net", + params: CreateASMapRequest{ + ASMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, - responseStatus: http.StatusOK, + responseStatus: http.StatusCreated, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/as-maps/The%20North", expectedResponse: &result, }, "500 internal server error": { - asMap: &req, - domainName: "example.akadns.net", + params: CreateASMapRequest{ + ASMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -246,7 +256,7 @@ func TestGTM_CreateASMap(t *testing.T) { result, err := client.CreateASMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.asMap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -258,7 +268,7 @@ func TestGTM_CreateASMap(t *testing.T) { } func TestGTM_UpdateASMap(t *testing.T) { - var result ASMapResponse + var result UpdateASMapResponse var req ASMap respData, err := loadTestData("TestGTM_CreateASMap.resp.json") @@ -280,29 +290,32 @@ func TestGTM_UpdateASMap(t *testing.T) { } tests := map[string]struct { - asMap *ASMap - domainName string + params UpdateASMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdateASMapResponse withError error headers http.Header }{ "200 OK": { - asMap: &req, - domainName: "example.akadns.net", + params: UpdateASMapRequest{ + ASMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/as-maps/The%20North", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - asMap: &req, - domainName: "example.akadns.net", + params: UpdateASMapRequest{ + ASMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -334,7 +347,7 @@ func TestGTM_UpdateASMap(t *testing.T) { result, err := client.UpdateASMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.asMap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -346,7 +359,7 @@ func TestGTM_UpdateASMap(t *testing.T) { } func TestGTM_DeleteASMap(t *testing.T) { - var result ASMapResponse + var result DeleteASMapResponse var req ASMap respData, err := loadTestData("TestGTM_CreateASMap.resp.json") @@ -368,29 +381,32 @@ func TestGTM_DeleteASMap(t *testing.T) { } tests := map[string]struct { - asMap *ASMap - domainName string + params DeleteASMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *DeleteASMapResponse withError error headers http.Header }{ "200 OK": { - asMap: &req, - domainName: "example.akadns.net", + params: DeleteASMapRequest{ + ASMapName: "The%20North", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/as-maps/The%20North", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - asMap: &req, - domainName: "example.akadns.net", + params: DeleteASMapRequest{ + ASMapName: "The%20North", + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -422,7 +438,7 @@ func TestGTM_DeleteASMap(t *testing.T) { result, err := client.DeleteASMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.asMap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/cidrmap.go b/pkg/gtm/cidrmap.go index 2f56e5c8..8f6457fa 100644 --- a/pkg/gtm/cidrmap.go +++ b/pkg/gtm/cidrmap.go @@ -2,70 +2,152 @@ package gtm import ( "context" + "errors" "fmt" "net/http" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) -// CIDRMaps contains operations available on a CIDR map resource. -type CIDRMaps interface { - // ListCIDRMaps retrieves all CIDRMaps. - // - // See: https://techdocs.akamai.com/gtm/reference/get-cidr-maps - ListCIDRMaps(context.Context, string) ([]*CIDRMap, error) - // GetCIDRMap retrieves a CIDRMap with the given name. - // - // See: https://techdocs.akamai.com/gtm/reference/get-cidr-map - GetCIDRMap(context.Context, string, string) (*CIDRMap, error) - // CreateCIDRMap creates the datacenter identified by the receiver argument in the specified domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-cidr-map - CreateCIDRMap(context.Context, *CIDRMap, string) (*CIDRMapResponse, error) - // DeleteCIDRMap deletes the datacenter identified by the receiver argument from the domain specified. - // - // See: https://techdocs.akamai.com/gtm/reference/delete-cidr-maps - DeleteCIDRMap(context.Context, *CIDRMap, string) (*ResponseStatus, error) - // UpdateCIDRMap updates the datacenter identified in the receiver argument in the provided domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-cidr-map - UpdateCIDRMap(context.Context, *CIDRMap, string) (*ResponseStatus, error) +type ( + // CIDRAssignment represents a GTM CIDR assignment element + CIDRAssignment struct { + DatacenterBase + Blocks []string `json:"blocks"` + } + + // CIDRMap represents a GTM CIDRMap element + CIDRMap struct { + DefaultDatacenter *DatacenterBase `json:"defaultDatacenter"` + Assignments []CIDRAssignment `json:"assignments,omitempty"` + Name string `json:"name"` + Links []Link `json:"links,omitempty"` + } + + // CIDRMapList represents a GTM returned CIDRMap list body + CIDRMapList struct { + CIDRMapItems []CIDRMap `json:"items"` + } + // ListCIDRMapsRequest contains request parameters for ListCIDRMaps + ListCIDRMapsRequest struct { + DomainName string + } + + // GetCIDRMapRequest contains request parameters for GetCIDRMap + GetCIDRMapRequest struct { + MapName string + DomainName string + } + + // GetCIDRMapResponse contains the response data from GetCIDRMap operation + GetCIDRMapResponse CIDRMap + + // CIDRMapRequest contains request parameters + CIDRMapRequest struct { + CIDR *CIDRMap + DomainName string + } + + // CreateCIDRMapRequest contains request parameters for CreateCIDRMap + CreateCIDRMapRequest CIDRMapRequest + + // CreateCIDRMapResponse contains the response data from CreateCIDRMap operation + CreateCIDRMapResponse struct { + Resource *CIDRMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // UpdateCIDRMapRequest contains request parameters for UpdateCIDRMap + UpdateCIDRMapRequest CIDRMapRequest + + // UpdateCIDRMapResponse contains the response data from UpdateCIDRMap operation + UpdateCIDRMapResponse struct { + Resource *CIDRMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // DeleteCIDRMapRequest contains request parameters for DeleteCIDRMap + DeleteCIDRMapRequest struct { + MapName string + DomainName string + } + + // DeleteCIDRMapResponse contains the response data from DeleteCIDRMap operation + DeleteCIDRMapResponse struct { + Resource *CIDRMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } +) + +var ( + // ErrListCIDRMaps is returned when ListCIDRMaps fails + ErrListCIDRMaps = errors.New("list cidrmaps") + // ErrGetCIDRMap is returned when GetCIDRMap fails + ErrGetCIDRMap = errors.New("get cidrmap") + // ErrCreateCIDRMap is returned when CreateCIDRMap fails + ErrCreateCIDRMap = errors.New("create cidrmap") + // ErrUpdateCIDRMap is returned when UpdateCIDRMap fails + ErrUpdateCIDRMap = errors.New("update cidrmap") + // ErrDeleteCIDRMap is returned when DeleteCIDRMap fails + ErrDeleteCIDRMap = errors.New("delete cidrmap") +) + +// Validate validates ListCIDRMapsRequest +func (r ListCIDRMapsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// CIDRAssignment represents a GTM CIDR assignment element -type CIDRAssignment struct { - DatacenterBase - Blocks []string `json:"blocks"` +// Validate validates GetCIDRMapRequest +func (r GetCIDRMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "MapName": validation.Validate(r.MapName, validation.Required), + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// CIDRMap represents a GTM CIDRMap element -type CIDRMap struct { - DefaultDatacenter *DatacenterBase `json:"defaultDatacenter"` - Assignments []*CIDRAssignment `json:"assignments,omitempty"` - Name string `json:"name"` - Links []*Link `json:"links,omitempty"` +// Validate validates CreateCIDRMapRequest +func (r CreateCIDRMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "CIDRMap": validation.Validate(r.CIDR, validation.Required), + }) } -// CIDRMapList represents a GTM returned CIDRMap list body -type CIDRMapList struct { - CIDRMapItems []*CIDRMap `json:"items"` +// Validate validates UpdateCIDRMapRequest +func (r UpdateCIDRMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "CIDRMap": validation.Validate(r.CIDR, validation.Required), + }) } -// Validate validates CIDRMap -func (c *CIDRMap) Validate() error { - if len(c.Name) < 1 { - return fmt.Errorf("CIDRMap is missing Name") - } - if c.DefaultDatacenter == nil { - return fmt.Errorf("CIDRMap is missing DefaultDatacenter") - } +// Validate validates DeleteCIDRMapRequest +func (r DeleteCIDRMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "MapName": validation.Validate(r.MapName, validation.Required), + }) +} - return nil +// Validate validates CIDRMap +func (c CIDRMap) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Name": validation.Validate(c.Name, validation.Required), + }) } -func (g *gtm) ListCIDRMaps(ctx context.Context, domainName string) ([]*CIDRMap, error) { +func (g *gtm) ListCIDRMaps(ctx context.Context, params ListCIDRMapsRequest) ([]CIDRMap, error) { logger := g.Log(ctx) logger.Debug("ListCIDRMaps") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrListCIDRMaps, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListCIDRMaps request: %w", err) @@ -85,18 +167,22 @@ func (g *gtm) ListCIDRMaps(ctx context.Context, domainName string) ([]*CIDRMap, return result.CIDRMapItems, nil } -func (g *gtm) GetCIDRMap(ctx context.Context, mapName, domainName string) (*CIDRMap, error) { +func (g *gtm) GetCIDRMap(ctx context.Context, params GetCIDRMapRequest) (*GetCIDRMapResponse, error) { logger := g.Log(ctx) logger.Debug("GetCIDRMap") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", domainName, mapName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetCIDRMap, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", params.DomainName, params.MapName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetCIDRMap request: %w", err) } setVersionHeader(req, schemaVersion) - var result CIDRMap + var result GetCIDRMapResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetCIDRMap request failed: %w", err) @@ -109,67 +195,78 @@ func (g *gtm) GetCIDRMap(ctx context.Context, mapName, domainName string) (*CIDR return &result, nil } -func (g *gtm) CreateCIDRMap(ctx context.Context, cidr *CIDRMap, domainName string) (*CIDRMapResponse, error) { +func (g *gtm) CreateCIDRMap(ctx context.Context, params CreateCIDRMapRequest) (*CreateCIDRMapResponse, error) { logger := g.Log(ctx) logger.Debug("CreateCIDRMap") - return cidr.save(ctx, g, domainName) -} + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateCIDRMap, ErrStructValidation, err) + } -func (g *gtm) UpdateCIDRMap(ctx context.Context, cidr *CIDRMap, domainName string) (*ResponseStatus, error) { - logger := g.Log(ctx) - logger.Debug("UpdateCIDRMap") + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", params.DomainName, params.CIDR.Name) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create CIDRMap request: %w", err) + } + setVersionHeader(req, schemaVersion) - stat, err := cidr.save(ctx, g, domainName) + var result CreateCIDRMapResponse + resp, err := g.Exec(req, &result, params.CIDR) if err != nil { - return nil, err + return nil, fmt.Errorf("CIDRMap request failed: %w", err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, g.Error(resp) } - return stat.Status, err + + return &result, nil } -// Save CIDRMap in given domain. Common path for Create and Update. -func (c *CIDRMap) save(ctx context.Context, g *gtm, domainName string) (*CIDRMapResponse, error) { +func (g *gtm) UpdateCIDRMap(ctx context.Context, params UpdateCIDRMapRequest) (*UpdateCIDRMapResponse, error) { + logger := g.Log(ctx) + logger.Debug("UpdateCIDRMap") - if err := c.Validate(); err != nil { - return nil, fmt.Errorf("CIDRMap validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateCIDRMap, ErrStructValidation, err) } - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", domainName, c.Name) + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", params.DomainName, params.CIDR.Name) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { return nil, fmt.Errorf("failed to create CIDRMap request: %w", err) } setVersionHeader(req, schemaVersion) - var result CIDRMapResponse - resp, err := g.Exec(req, &result, c) + var result UpdateCIDRMapResponse + resp, err := g.Exec(req, &result, params.CIDR) if err != nil { return nil, fmt.Errorf("CIDRMap request failed: %w", err) } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusOK { return nil, g.Error(resp) } return &result, nil } -func (g *gtm) DeleteCIDRMap(ctx context.Context, cidr *CIDRMap, domainName string) (*ResponseStatus, error) { +func (g *gtm) DeleteCIDRMap(ctx context.Context, params DeleteCIDRMapRequest) (*DeleteCIDRMapResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteCIDRMap") - if err := cidr.Validate(); err != nil { - return nil, fmt.Errorf("CIDRMap validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteCIDRMap, ErrStructValidation, err) } - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", domainName, cidr.Name) + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/cidr-maps/%s", params.DomainName, params.MapName) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Delete request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseBody + var result DeleteCIDRMapResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("CIDRMap request failed: %w", err) @@ -179,5 +276,5 @@ func (g *gtm) DeleteCIDRMap(ctx context.Context, cidr *CIDRMap, domainName strin return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } diff --git a/pkg/gtm/cidrmap_test.go b/pkg/gtm/cidrmap_test.go index 17fa558d..1e821955 100644 --- a/pkg/gtm/cidrmap_test.go +++ b/pkg/gtm/cidrmap_test.go @@ -5,11 +5,12 @@ import ( "context" "encoding/json" "errors" + "io" "net/http" "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,16 +28,18 @@ func TestGTM_ListCIDRMaps(t *testing.T) { } tests := map[string]struct { - domainName string + params ListCIDRMapsRequest responseStatus int responseBody string expectedPath string - expectedResponse []*CIDRMap + expectedResponse []CIDRMap withError error headers http.Header }{ "200 OK": { - domainName: "example.akadns.net", + params: ListCIDRMapsRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -46,7 +49,9 @@ func TestGTM_ListCIDRMaps(t *testing.T) { expectedResponse: result.CIDRMapItems, }, "500 internal server error": { - domainName: "example.akadns.net", + params: ListCIDRMapsRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -79,7 +84,7 @@ func TestGTM_ListCIDRMaps(t *testing.T) { result, err := client.ListCIDRMaps( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -91,7 +96,7 @@ func TestGTM_ListCIDRMaps(t *testing.T) { } func TestGTM_GetCIDRMap(t *testing.T) { - var result CIDRMap + var result GetCIDRMapResponse respData, err := loadTestData("TestGTM_GetCIDRMap.resp.json") if err != nil { @@ -103,18 +108,19 @@ func TestGTM_GetCIDRMap(t *testing.T) { } tests := map[string]struct { - name string - domainName string + params GetCIDRMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *CIDRMap + expectedResponse *GetCIDRMapResponse withError error headers http.Header }{ "200 OK": { - name: "Software-rollout", - domainName: "example.akadns.net", + params: GetCIDRMapRequest{ + MapName: "Software-rollout", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -124,8 +130,10 @@ func TestGTM_GetCIDRMap(t *testing.T) { expectedResponse: &result, }, "500 internal server error": { - name: "Software-rollout", - domainName: "example.akadns.net", + params: GetCIDRMapRequest{ + MapName: "Software-rollout", + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -158,7 +166,7 @@ func TestGTM_GetCIDRMap(t *testing.T) { result, err := client.GetCIDRMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.name, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -170,7 +178,7 @@ func TestGTM_GetCIDRMap(t *testing.T) { } func TestGTM_CreateCIDRMap(t *testing.T) { - var result CIDRMapResponse + var result CreateCIDRMapResponse var req CIDRMap respData, err := loadTestData("TestGTM_CreateCIDRMap.resp.json") @@ -192,29 +200,85 @@ func TestGTM_CreateCIDRMap(t *testing.T) { } tests := map[string]struct { - cmap *CIDRMap - domainName string - responseStatus int - responseBody string - expectedPath string - expectedResponse *CIDRMapResponse - withError error - headers http.Header + params CreateCIDRMapRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *CreateCIDRMapResponse + expectedRequestBody string + withError error + headers http.Header }{ "200 OK": { - cmap: &req, - domainName: "example.akadns.net", + params: CreateCIDRMapRequest{ + CIDR: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, - responseStatus: http.StatusOK, + responseStatus: http.StatusCreated, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/cidr-maps/The%20North", expectedResponse: &result, + expectedRequestBody: `{ + "name": "The North", + "defaultDatacenter": { + "datacenterId": 5400, + "nickname": "All Other CIDR Blocks" + }, + "assignments": [ + { + "datacenterId": 3134, + "nickname": "Frostfangs and the Fist of First Men", + "blocks": [ + "1.3.5.9", + "1.2.3.0/24" + ] + }, + { + "datacenterId": 3133, + "nickname": "Winterfell", + "blocks": [ + "1.2.4.0/24" + ] + } + ] +}`, + }, + "200 test": { + params: CreateCIDRMapRequest{ + CIDR: &CIDRMap{ + DefaultDatacenter: &DatacenterBase{ + Nickname: "test_datacenter", + DatacenterID: 200, + }, + Assignments: nil, + Name: "test_name", + Links: nil, + }, + DomainName: "example.akadns.net", + }, + headers: http.Header{ + "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, + }, + responseStatus: http.StatusCreated, + responseBody: string(respData), + expectedPath: "/config-gtm/v1/domains/example.akadns.net/cidr-maps/test_name", + expectedResponse: &result, + expectedRequestBody: `{ + "name": "test_name", + "defaultDatacenter": { + "datacenterId": 200, + "nickname": "test_datacenter" + } +}`, }, "500 internal server error": { - cmap: &req, - domainName: "example.akadns.net", + params: CreateCIDRMapRequest{ + CIDR: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -241,12 +305,17 @@ func TestGTM_CreateCIDRMap(t *testing.T) { w.WriteHeader(test.responseStatus) _, err := w.Write([]byte(test.responseBody)) assert.NoError(t, err) + if test.expectedRequestBody != "" { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.JSONEq(t, test.expectedRequestBody, string(body)) + } })) client := mockAPIClient(t, mockServer) result, err := client.CreateCIDRMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.cmap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -258,7 +327,7 @@ func TestGTM_CreateCIDRMap(t *testing.T) { } func TestGTM_UpdateCIDRMap(t *testing.T) { - var result CIDRMapResponse + var result UpdateCIDRMapResponse var req CIDRMap respData, err := loadTestData("TestGTM_CreateCIDRMap.resp.json") @@ -280,29 +349,32 @@ func TestGTM_UpdateCIDRMap(t *testing.T) { } tests := map[string]struct { - cmap *CIDRMap - domainName string + params UpdateCIDRMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdateCIDRMapResponse withError error headers http.Header }{ "200 OK": { - cmap: &req, - domainName: "example.akadns.net", + params: UpdateCIDRMapRequest{ + CIDR: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/cidr-maps/The%20North", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - cmap: &req, - domainName: "example.akadns.net", + params: UpdateCIDRMapRequest{ + CIDR: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -334,7 +406,7 @@ func TestGTM_UpdateCIDRMap(t *testing.T) { result, err := client.UpdateCIDRMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.cmap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -346,7 +418,7 @@ func TestGTM_UpdateCIDRMap(t *testing.T) { } func TestGTM_DeleteCIDRMap(t *testing.T) { - var result CIDRMapResponse + var result DeleteCIDRMapResponse var req CIDRMap respData, err := loadTestData("TestGTM_CreateCIDRMap.resp.json") @@ -368,29 +440,32 @@ func TestGTM_DeleteCIDRMap(t *testing.T) { } tests := map[string]struct { - cmap *CIDRMap - domainName string + params DeleteCIDRMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *DeleteCIDRMapResponse withError error headers http.Header }{ "200 OK": { - cmap: &req, - domainName: "example.akadns.net", + params: DeleteCIDRMapRequest{ + MapName: "The%20North", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/cidr-maps/The%20North", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - cmap: &req, - domainName: "example.akadns.net", + params: DeleteCIDRMapRequest{ + MapName: "The%20North", + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -422,7 +497,7 @@ func TestGTM_DeleteCIDRMap(t *testing.T) { result, err := client.DeleteCIDRMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.cmap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/common.go b/pkg/gtm/common.go index c83c5858..dd172663 100644 --- a/pkg/gtm/common.go +++ b/pkg/gtm/common.go @@ -23,24 +23,12 @@ func setVersionHeader(req *http.Request, version string) { // ResponseStatus is returned on Create, Update or Delete operations for all entity types type ResponseStatus struct { - ChangeID string `json:"changeId,omitempty"` - Links *[]Link `json:"links,omitempty"` - Message string `json:"message,omitempty"` - PassingValidation bool `json:"passingValidation,omitempty"` - PropagationStatus string `json:"propagationStatus,omitempty"` - PropagationStatusDate string `json:"propagationStatusDate,omitempty"` -} - -// ResponseBody is a generic response struct -type ResponseBody struct { - Resource interface{} `json:"resource"` - Status *ResponseStatus `json:"status"` -} - -// DomainResponse contains a response after creating or updating Domain -type DomainResponse struct { - Resource *Domain `json:"resource"` - Status *ResponseStatus `json:"status"` + ChangeID string `json:"changeId,omitempty"` + Links []Link `json:"links,omitempty"` + Message string `json:"message,omitempty"` + PassingValidation bool `json:"passingValidation,omitempty"` + PropagationStatus string `json:"propagationStatus,omitempty"` + PropagationStatusDate string `json:"propagationStatusDate,omitempty"` } // DatacenterResponse contains a response after creating or updating Datacenter @@ -49,40 +37,16 @@ type DatacenterResponse struct { Resource *Datacenter `json:"resource"` } -// PropertyResponse contains a response after creating or updating Property -type PropertyResponse struct { - Resource *Property `json:"resource"` - Status *ResponseStatus `json:"status"` -} - // ResourceResponse contains a response after creating or updating Resource type ResourceResponse struct { Resource *Resource `json:"resource"` Status *ResponseStatus `json:"status"` } -// CIDRMapResponse contains a response after creating or updating CIDRMap -type CIDRMapResponse struct { - Resource *CIDRMap `json:"resource"` - Status *ResponseStatus `json:"status"` -} - -// GeoMapResponse contains a response after creating or updating GeoMap -type GeoMapResponse struct { - Resource *GeoMap `json:"resource"` - Status *ResponseStatus `json:"status"` -} - -// ASMapResponse contains a response after creating or updating ASMap -type ASMapResponse struct { - Resource *ASMap `json:"resource"` - Status *ResponseStatus `json:"status"` -} - // Link is Probably THE most common type type Link struct { - Rel string `json:"rel"` - Href string `json:"href"` + Rel string `json:"rel,omitempty"` + Href string `json:"href,omitempty"` } // LoadObject contains information about the load reporting interface diff --git a/pkg/gtm/datacenter.go b/pkg/gtm/datacenter.go index 39b52078..e6831407 100644 --- a/pkg/gtm/datacenter.go +++ b/pkg/gtm/datacenter.go @@ -2,76 +2,161 @@ package gtm import ( "context" + "errors" "fmt" "net/http" - "strconv" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // Datacenter represents a GTM datacenter + Datacenter struct { + City string `json:"city,omitempty"` + CloneOf int `json:"cloneOf,omitempty"` + CloudServerHostHeaderOverride bool `json:"cloudServerHostHeaderOverride"` + CloudServerTargeting bool `json:"cloudServerTargeting"` + Continent string `json:"continent,omitempty"` + Country string `json:"country,omitempty"` + DefaultLoadObject *LoadObject `json:"defaultLoadObject,omitempty"` + Latitude float64 `json:"latitude,omitempty"` + Links []Link `json:"links,omitempty"` + Longitude float64 `json:"longitude,omitempty"` + Nickname string `json:"nickname,omitempty"` + PingInterval int `json:"pingInterval,omitempty"` + PingPacketSize int `json:"pingPacketSize,omitempty"` + DatacenterID int `json:"datacenterId,omitempty"` + ScorePenalty int `json:"scorePenalty,omitempty"` + ServermonitorLivenessCount int `json:"servermonitorLivenessCount,omitempty"` + ServermonitorLoadCount int `json:"servermonitorLoadCount,omitempty"` + ServermonitorPool string `json:"servermonitorPool,omitempty"` + StateOrProvince string `json:"stateOrProvince,omitempty"` + Virtual bool `json:"virtual"` + } + + // DatacenterList contains a list of Datacenters + DatacenterList struct { + DatacenterItems []Datacenter `json:"items"` + } + + // ListDatacentersRequest contains request parameters for ListDatacenters + ListDatacentersRequest struct { + DomainName string + } + + // GetDatacenterRequest contains request parameters for GetDatacenter + GetDatacenterRequest struct { + DatacenterID int + DomainName string + } + + // DatacenterRequest contains request parameters + DatacenterRequest struct { + Datacenter *Datacenter + DomainName string + } + + // CreateDatacenterRequest contains request parameters for CreateDatacenter + CreateDatacenterRequest DatacenterRequest + + // CreateDatacenterResponse contains the response data from CreateDatacenter operation + CreateDatacenterResponse struct { + Status *ResponseStatus `json:"status"` + Resource *Datacenter `json:"resource"` + } + + // UpdateDatacenterRequest contains request parameters for UpdateDatacenter + UpdateDatacenterRequest DatacenterRequest + + // UpdateDatacenterResponse contains the response data from UpdateDatacenter operation + UpdateDatacenterResponse struct { + Status *ResponseStatus `json:"status"` + Resource *Datacenter `json:"resource"` + } + + // DeleteDatacenterRequest contains request parameters for DeleteDatacenter + DeleteDatacenterRequest struct { + DatacenterID int + DomainName string + } + + // DeleteDatacenterResponse contains the response data from DeleteDatacenter operation + DeleteDatacenterResponse struct { + Status *ResponseStatus `json:"status"` + Resource *Datacenter `json:"resource"` + } +) + +var ( + // ErrListDatacenters is returned when ListDatacenters fails + ErrListDatacenters = errors.New("list datacenters") + // ErrGetDatacenter is returned when GetDatacenter fails + ErrGetDatacenter = errors.New("get datacenter") + // ErrCreateDatacenter is returned when CreateDatacenter fails + ErrCreateDatacenter = errors.New("create datacenter") + // ErrUpdateDatacenter is returned when UpdateDatacenter fails + ErrUpdateDatacenter = errors.New("update datacenter") + // ErrDeleteDatacenter is returned when DeleteDatacenter fails + ErrDeleteDatacenter = errors.New("delete datacenter") ) -// Datacenters contains operations available on a Datacenter resource. -type Datacenters interface { - // ListDatacenters retrieves all Datacenters. - // - // See: https://techdocs.akamai.com/gtm/reference/get-datacenters - ListDatacenters(context.Context, string) ([]*Datacenter, error) - // GetDatacenter retrieves a Datacenter with the given name. NOTE: Id arg is int! - // - // See: https://techdocs.akamai.com/gtm/reference/get-datacenter - GetDatacenter(context.Context, int, string) (*Datacenter, error) - // CreateDatacenter creates the datacenter identified by the receiver argument in the specified domain. - // - // See: https://techdocs.akamai.com/gtm/reference/post-datacenter - CreateDatacenter(context.Context, *Datacenter, string) (*DatacenterResponse, error) - // DeleteDatacenter deletes the datacenter identified by the receiver argument from the domain specified. - // - // See: https://techdocs.akamai.com/gtm/reference/delete-datacenter - DeleteDatacenter(context.Context, *Datacenter, string) (*ResponseStatus, error) - // UpdateDatacenter updates the datacenter identified in the receiver argument in the provided domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-datacenter - UpdateDatacenter(context.Context, *Datacenter, string) (*ResponseStatus, error) - // CreateMapsDefaultDatacenter creates Default Datacenter for Maps. - CreateMapsDefaultDatacenter(context.Context, string) (*Datacenter, error) - // CreateIPv4DefaultDatacenter creates Default Datacenter for IPv4 Selector. - CreateIPv4DefaultDatacenter(context.Context, string) (*Datacenter, error) - // CreateIPv6DefaultDatacenter creates Default Datacenter for IPv6 Selector. - CreateIPv6DefaultDatacenter(context.Context, string) (*Datacenter, error) +// Validate validates ListDatacentersRequest +func (r ListDatacentersRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) +} + +// Validate validates GetDatacenterRequest +func (r GetDatacenterRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DatacenterID": validation.Validate(r.DatacenterID, validation.Required), + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) +} + +// Validate validates CreateDatacenterRequest +func (r CreateDatacenterRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "Datacenter": validation.Validate(r.Datacenter, validation.Required), + }) } -// Datacenter represents a GTM datacenter -type Datacenter struct { - City string `json:"city,omitempty"` - CloneOf int `json:"cloneOf,omitempty"` - CloudServerHostHeaderOverride bool `json:"cloudServerHostHeaderOverride"` - CloudServerTargeting bool `json:"cloudServerTargeting"` - Continent string `json:"continent,omitempty"` - Country string `json:"country,omitempty"` - DefaultLoadObject *LoadObject `json:"defaultLoadObject,omitempty"` - Latitude float64 `json:"latitude,omitempty"` - Links []*Link `json:"links,omitempty"` - Longitude float64 `json:"longitude,omitempty"` - Nickname string `json:"nickname,omitempty"` - PingInterval int `json:"pingInterval,omitempty"` - PingPacketSize int `json:"pingPacketSize,omitempty"` - DatacenterID int `json:"datacenterId,omitempty"` - ScorePenalty int `json:"scorePenalty,omitempty"` - ServermonitorLivenessCount int `json:"servermonitorLivenessCount,omitempty"` - ServermonitorLoadCount int `json:"servermonitorLoadCount,omitempty"` - ServermonitorPool string `json:"servermonitorPool,omitempty"` - StateOrProvince string `json:"stateOrProvince,omitempty"` - Virtual bool `json:"virtual"` +// Validate validates UpdateDatacenterRequest +func (r UpdateDatacenterRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "Datacenter": validation.Validate(r.Datacenter, validation.Required), + }) } -// DatacenterList contains a list of Datacenters -type DatacenterList struct { - DatacenterItems []*Datacenter `json:"items"` +// Validate validates DeleteDatacenterRequest +func (r DeleteDatacenterRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "DatacenterID": validation.Validate(r.DatacenterID, validation.Required), + }) } -func (g *gtm) ListDatacenters(ctx context.Context, domainName string) ([]*Datacenter, error) { +// Validate validates Datacenter +func (d Datacenter) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DatacenterID": validation.Validate(d.DatacenterID), + }) +} + +func (g *gtm) ListDatacenters(ctx context.Context, params ListDatacentersRequest) ([]Datacenter, error) { logger := g.Log(ctx) logger.Debug("ListDatacenters") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrListDatacenters, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListDatacenters request: %w", err) @@ -91,11 +176,15 @@ func (g *gtm) ListDatacenters(ctx context.Context, domainName string) ([]*Datace return result.DatacenterItems, nil } -func (g *gtm) GetDatacenter(ctx context.Context, dcID int, domainName string) (*Datacenter, error) { +func (g *gtm) GetDatacenter(ctx context.Context, params GetDatacenterRequest) (*Datacenter, error) { logger := g.Log(ctx) logger.Debug("GetDatacenter") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters/%s", domainName, strconv.Itoa(dcID)) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetDatacenter, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters/%s", params.DomainName, strconv.Itoa(params.DatacenterID)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetDatacenter request: %w", err) @@ -115,19 +204,23 @@ func (g *gtm) GetDatacenter(ctx context.Context, dcID int, domainName string) (* return &result, nil } -func (g *gtm) CreateDatacenter(ctx context.Context, dc *Datacenter, domainName string) (*DatacenterResponse, error) { +func (g *gtm) CreateDatacenter(ctx context.Context, params CreateDatacenterRequest) (*CreateDatacenterResponse, error) { logger := g.Log(ctx) logger.Debug("CreateDatacenter") - postURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateDatacenter, ErrStructValidation, err) + } + + postURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Datacenter request: %w", err) } setVersionHeader(req, schemaVersion) - var result DatacenterResponse - resp, err := g.Exec(req, &result, dc) + var result CreateDatacenterResponse + resp, err := g.Exec(req, &result, params.Datacenter) if err != nil { return nil, fmt.Errorf("CreateDatacenter request failed: %w", err) } @@ -173,8 +266,12 @@ func createDefaultDC(ctx context.Context, g *gtm, defaultID int, domainName stri if defaultID != MapDefaultDC && defaultID != Ipv4DefaultDC && defaultID != Ipv6DefaultDC { return nil, fmt.Errorf("invalid default datacenter id provided for creation") } + // check if already exists - dc, err := g.GetDatacenter(ctx, defaultID, domainName) + dc, err := g.GetDatacenter(ctx, GetDatacenterRequest{ + DatacenterID: defaultID, + DomainName: domainName, + }) if err == nil { return dc, err } @@ -211,19 +308,23 @@ func createDefaultDC(ctx context.Context, g *gtm, defaultID int, domainName stri return result.Resource, nil } -func (g *gtm) UpdateDatacenter(ctx context.Context, dc *Datacenter, domainName string) (*ResponseStatus, error) { +func (g *gtm) UpdateDatacenter(ctx context.Context, params UpdateDatacenterRequest) (*UpdateDatacenterResponse, error) { logger := g.Log(ctx) logger.Debug("UpdateDatacenter") - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters/%s", domainName, strconv.Itoa(dc.DatacenterID)) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateDatacenter, ErrStructValidation, err) + } + + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters/%s", params.DomainName, strconv.Itoa(params.Datacenter.DatacenterID)) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Update Datacenter request: %w", err) } setVersionHeader(req, schemaVersion) - var result DatacenterResponse - resp, err := g.Exec(req, &result, dc) + var result UpdateDatacenterResponse + resp, err := g.Exec(req, &result, params.Datacenter) if err != nil { return nil, fmt.Errorf("UpdateDatacenter request failed: %w", err) } @@ -231,21 +332,25 @@ func (g *gtm) UpdateDatacenter(ctx context.Context, dc *Datacenter, domainName s return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } -func (g *gtm) DeleteDatacenter(ctx context.Context, dc *Datacenter, domainName string) (*ResponseStatus, error) { +func (g *gtm) DeleteDatacenter(ctx context.Context, params DeleteDatacenterRequest) (*DeleteDatacenterResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteDatacenter") - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters/%s", domainName, strconv.Itoa(dc.DatacenterID)) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteDatacenter, ErrStructValidation, err) + } + + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/datacenters/%s", params.DomainName, strconv.Itoa(params.DatacenterID)) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Delete Datacenter request: %w", err) } setVersionHeader(req, schemaVersion) - var result DatacenterResponse + var result DeleteDatacenterResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("DeleteDatacenter request failed: %w", err) @@ -254,5 +359,5 @@ func (g *gtm) DeleteDatacenter(ctx context.Context, dc *Datacenter, domainName s return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } diff --git a/pkg/gtm/datacenter_test.go b/pkg/gtm/datacenter_test.go index b6f894c8..4391dfc8 100644 --- a/pkg/gtm/datacenter_test.go +++ b/pkg/gtm/datacenter_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,16 +27,18 @@ func TestGTM_ListDatacenters(t *testing.T) { } tests := map[string]struct { - domainName string + params ListDatacentersRequest responseStatus int responseBody string expectedPath string - expectedResponse []*Datacenter + expectedResponse []Datacenter withError error headers http.Header }{ "200 OK": { - domainName: "example.akadns.net", + params: ListDatacentersRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -46,7 +48,9 @@ func TestGTM_ListDatacenters(t *testing.T) { expectedResponse: result.DatacenterItems, }, "500 internal server error": { - domainName: "example.akadns.net", + params: ListDatacentersRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -78,7 +82,7 @@ func TestGTM_ListDatacenters(t *testing.T) { result, err := client.ListDatacenters( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -102,8 +106,7 @@ func TestGTM_GetDatacenter(t *testing.T) { } tests := map[string]struct { - id int - domainName string + params GetDatacenterRequest responseStatus int responseBody string expectedPath string @@ -112,8 +115,10 @@ func TestGTM_GetDatacenter(t *testing.T) { headers http.Header }{ "200 OK": { - id: 1, - domainName: "example.akadns.net", + params: GetDatacenterRequest{ + DatacenterID: 1, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -123,8 +128,10 @@ func TestGTM_GetDatacenter(t *testing.T) { expectedResponse: &result, }, "500 internal server error": { - id: 1, - domainName: "example.akadns.net", + params: GetDatacenterRequest{ + DatacenterID: 1, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -156,7 +163,7 @@ func TestGTM_GetDatacenter(t *testing.T) { result, err := client.GetDatacenter( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.id, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -168,7 +175,7 @@ func TestGTM_GetDatacenter(t *testing.T) { } func TestGTM_CreateDatacenter(t *testing.T) { - var result DatacenterResponse + var result CreateDatacenterResponse var req Datacenter respData, err := loadTestData("TestGTM_CreateDatacenter.resp.json") @@ -190,18 +197,19 @@ func TestGTM_CreateDatacenter(t *testing.T) { } tests := map[string]struct { - dc *Datacenter - domainName string + params CreateDatacenterRequest responseStatus int responseBody string expectedPath string - expectedResponse *DatacenterResponse + expectedResponse *CreateDatacenterResponse withError error headers http.Header }{ "200 OK": { - dc: &req, - domainName: "example.akadns.net", + params: CreateDatacenterRequest{ + Datacenter: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -211,8 +219,10 @@ func TestGTM_CreateDatacenter(t *testing.T) { expectedResponse: &result, }, "500 internal server error": { - dc: &req, - domainName: "example.akadns.net", + params: CreateDatacenterRequest{ + Datacenter: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -244,7 +254,7 @@ func TestGTM_CreateDatacenter(t *testing.T) { result, err := client.CreateDatacenter( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.dc, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -511,7 +521,7 @@ func TestGTM_CreateIPv6DefaultDatacenter(t *testing.T) { } func TestGTM_UpdateDatacenter(t *testing.T) { - var result DatacenterResponse + var result UpdateDatacenterResponse var req Datacenter respData, err := loadTestData("TestGTM_CreateDatacenter.resp.json") @@ -533,29 +543,32 @@ func TestGTM_UpdateDatacenter(t *testing.T) { } tests := map[string]struct { - dc *Datacenter - domainName string + params UpdateDatacenterRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdateDatacenterResponse withError error headers http.Header }{ "200 OK": { - dc: &req, - domainName: "example.akadns.net", + params: UpdateDatacenterRequest{ + Datacenter: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/datacenters/0", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - dc: &req, - domainName: "example.akadns.net", + params: UpdateDatacenterRequest{ + Datacenter: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -587,7 +600,7 @@ func TestGTM_UpdateDatacenter(t *testing.T) { result, err := client.UpdateDatacenter( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.dc, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -599,7 +612,7 @@ func TestGTM_UpdateDatacenter(t *testing.T) { } func TestGTM_DeleteDatacenter(t *testing.T) { - var result DatacenterResponse + var result DeleteDatacenterResponse var req Datacenter respData, err := loadTestData("TestGTM_CreateDatacenter.resp.json") @@ -621,29 +634,32 @@ func TestGTM_DeleteDatacenter(t *testing.T) { } tests := map[string]struct { - dc *Datacenter - domainName string + params DeleteDatacenterRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *DeleteDatacenterResponse withError error headers http.Header }{ "200 OK": { - dc: &req, - domainName: "example.akadns.net", + params: DeleteDatacenterRequest{ + DatacenterID: 1, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), - expectedPath: "/config-gtm/v1/domains/example.akadns.net/datacenters/0", - expectedResponse: result.Status, + expectedPath: "/config-gtm/v1/domains/example.akadns.net/datacenters/1", + expectedResponse: &result, }, "500 internal server error": { - dc: &req, - domainName: "example.akadns.net", + params: DeleteDatacenterRequest{ + DatacenterID: 1, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -652,7 +668,7 @@ func TestGTM_DeleteDatacenter(t *testing.T) { "title": "Internal Server Error", "detail": "Error updating dc" }`, - expectedPath: "/config-gtm/v1/domains/example.akadns.net/datacenters/0", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/datacenters/1", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -675,7 +691,7 @@ func TestGTM_DeleteDatacenter(t *testing.T) { result, err := client.DeleteDatacenter( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.dc, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/domain.go b/pkg/gtm/domain.go index 2ea8af34..7ecb6e97 100644 --- a/pkg/gtm/domain.go +++ b/pkg/gtm/domain.go @@ -2,110 +2,191 @@ package gtm import ( "context" + "errors" "fmt" "net/http" - "reflect" - "strings" "unicode" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) -// Domains contains operations available on a Domain resource. -type Domains interface { - // NullFieldMap retrieves map of null fields. - NullFieldMap(context.Context, *Domain) (*NullFieldMapStruct, error) - // GetDomainStatus retrieves current status for the given domain name. - // - // See: https://techdocs.akamai.com/gtm/reference/get-status-current - GetDomainStatus(context.Context, string) (*ResponseStatus, error) - // ListDomains retrieves all Domains. - // - // See: https://techdocs.akamai.com/gtm/reference/get-domains - ListDomains(context.Context) ([]*DomainItem, error) - // GetDomain retrieves a Domain with the given domain name. - // - // See: https://techdocs.akamai.com/gtm/reference/get-domain - GetDomain(context.Context, string) (*Domain, error) - // CreateDomain creates domain. - // - // See: https://techdocs.akamai.com/gtm/reference/post-domain - CreateDomain(context.Context, *Domain, map[string]string) (*DomainResponse, error) - // DeleteDomain is a method applied to a domain object resulting in removal. - // - // See: ** Not Supported by API ** - DeleteDomain(context.Context, *Domain) (*ResponseStatus, error) - // UpdateDomain is a method applied to a domain object resulting in an update. - // - // See: https://techdocs.akamai.com/gtm/reference/put-domain - UpdateDomain(context.Context, *Domain, map[string]string) (*ResponseStatus, error) +// The Domain data structure represents a GTM domain +type ( + Domain struct { + Name string `json:"name"` + Type string `json:"type"` + ASMaps []ASMap `json:"asMaps,omitempty"` + Resources []Resource `json:"resources,omitempty"` + DefaultUnreachableThreshold float32 `json:"defaultUnreachableThreshold,omitempty"` + EmailNotificationList []string `json:"emailNotificationList,omitempty"` + MinPingableRegionFraction float32 `json:"minPingableRegionFraction,omitempty"` + DefaultTimeoutPenalty int `json:"defaultTimeoutPenalty,omitempty"` + Datacenters []Datacenter `json:"datacenters,omitempty"` + ServermonitorLivenessCount int `json:"servermonitorLivenessCount,omitempty"` + RoundRobinPrefix string `json:"roundRobinPrefix,omitempty"` + ServermonitorLoadCount int `json:"servermonitorLoadCount,omitempty"` + PingInterval int `json:"pingInterval,omitempty"` + MaxTTL int64 `json:"maxTTL,omitempty"` + LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` + DefaultHealthMax float64 `json:"defaultHealthMax,omitempty"` + LastModified string `json:"lastModified,omitempty"` + Status *ResponseStatus `json:"status,omitempty"` + MapUpdateInterval int `json:"mapUpdateInterval,omitempty"` + MaxProperties int `json:"maxProperties,omitempty"` + MaxResources int `json:"maxResources,omitempty"` + DefaultSSLClientPrivateKey string `json:"defaultSslClientPrivateKey,omitempty"` + DefaultErrorPenalty int `json:"defaultErrorPenalty,omitempty"` + Links []Link `json:"links,omitempty"` + Properties []Property `json:"properties,omitempty"` + MaxTestTimeout float64 `json:"maxTestTimeout,omitempty"` + CNameCoalescingEnabled bool `json:"cnameCoalescingEnabled"` + DefaultHealthMultiplier float64 `json:"defaultHealthMultiplier,omitempty"` + ServermonitorPool string `json:"servermonitorPool,omitempty"` + LoadFeedback bool `json:"loadFeedback"` + MinTTL int64 `json:"minTTL,omitempty"` + GeographicMaps []GeoMap `json:"geographicMaps,omitempty"` + CIDRMaps []CIDRMap `json:"cidrMaps,omitempty"` + DefaultMaxUnreachablePenalty int `json:"defaultMaxUnreachablePenalty"` + DefaultHealthThreshold float64 `json:"defaultHealthThreshold,omitempty"` + LastModifiedBy string `json:"lastModifiedBy,omitempty"` + ModificationComments string `json:"modificationComments,omitempty"` + MinTestInterval int `json:"minTestInterval,omitempty"` + PingPacketSize int `json:"pingPacketSize,omitempty"` + DefaultSSLClientCertificate string `json:"defaultSslClientCertificate,omitempty"` + EndUserMappingEnabled bool `json:"endUserMappingEnabled"` + SignAndServe bool `json:"signAndServe"` + SignAndServeAlgorithm *string `json:"signAndServeAlgorithm"` + } + + // DomainQueryArgs contains query parameters for domain request + DomainQueryArgs struct { + ContractID string + GroupID string + } + + // DomainsList contains a list of domain items + DomainsList struct { + DomainItems []DomainItem `json:"items"` + } + + // DomainItem is a DomainsList item + DomainItem struct { + AcgID string `json:"acgId"` + LastModified string `json:"lastModified"` + Links []Link `json:"links"` + Name string `json:"name"` + Status string `json:"status"` + LastModifiedBy string `json:"lastModifiedBy"` + ChangeID string `json:"changeId"` + ActivationState string `json:"activationState"` + ModificationComments string `json:"modificationComments"` + SignAndServe bool `json:"signAndServe"` + SignAndServeAlgorithm string `json:"signAndServeAlgorithm"` + DeleteRequestID string `json:"deleteRequestId"` + } + // GetDomainStatusRequest contains request parameters for GetDomainStatus + GetDomainStatusRequest struct { + DomainName string + } + // GetDomainStatusResponse contains the response data from GetDomainStatus operation + GetDomainStatusResponse ResponseStatus + + // DomainRequest contains request parameters + DomainRequest struct { + Domain *Domain + QueryArgs *DomainQueryArgs + } + + // GetDomainRequest contains request parameters for GetDomain + GetDomainRequest struct { + DomainName string + } + + // GetDomainResponse contains the response data from GetDomain operation + GetDomainResponse Domain + + // CreateDomainRequest contains request parameters for CreateDomain + CreateDomainRequest DomainRequest + + // CreateDomainResponse contains the response data from CreateDomain operation + CreateDomainResponse struct { + Resource *Domain `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // UpdateDomainRequest contains request parameters for UpdateDomain + UpdateDomainRequest DomainRequest + + // UpdateDomainResponse contains the response data from UpdateDomain operation + UpdateDomainResponse struct { + Resource *Domain `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // DeleteDomainRequest contains request parameters for DeleteDomain + DeleteDomainRequest struct { + DomainName string + } + + // DeleteDomainResponse contains request parameters for DeleteDomain + DeleteDomainResponse struct { + ChangeID string `json:"changeId,omitempty"` + Links []Link `json:"links,omitempty"` + Message string `json:"message,omitempty"` + PassingValidation bool `json:"passingValidation,omitempty"` + PropagationStatus string `json:"propagationStatus,omitempty"` + PropagationStatusDate string `json:"propagationStatusDate,omitempty"` + } +) + +var ( + // ErrGetDomainStatus is returned when GetDomainStatus fails + ErrGetDomainStatus = errors.New("get domain status") + // ErrGetDomain is returned when GetDomain fails + ErrGetDomain = errors.New("get domain") + // ErrCreateDomain is returned when CreateDomain fails + ErrCreateDomain = errors.New("create domain") + // ErrUpdateDomain is returned when UpdateDomain fails + ErrUpdateDomain = errors.New("update domain") + // ErrDeleteDomain is returned when DeleteDomain fails + ErrDeleteDomain = errors.New("delete domain") +) + +// Validate validates DeleteDomainRequest +func (r DeleteDomainRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// The Domain data structure represents a GTM domain -type Domain struct { - Name string `json:"name"` - Type string `json:"type"` - ASMaps []*ASMap `json:"asMaps,omitempty"` - Resources []*Resource `json:"resources,omitempty"` - DefaultUnreachableThreshold float32 `json:"defaultUnreachableThreshold,omitempty"` - EmailNotificationList []string `json:"emailNotificationList,omitempty"` - MinPingableRegionFraction float32 `json:"minPingableRegionFraction,omitempty"` - DefaultTimeoutPenalty int `json:"defaultTimeoutPenalty,omitempty"` - Datacenters []*Datacenter `json:"datacenters,omitempty"` - ServermonitorLivenessCount int `json:"servermonitorLivenessCount,omitempty"` - RoundRobinPrefix string `json:"roundRobinPrefix,omitempty"` - ServermonitorLoadCount int `json:"servermonitorLoadCount,omitempty"` - PingInterval int `json:"pingInterval,omitempty"` - MaxTTL int64 `json:"maxTTL,omitempty"` - LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` - DefaultHealthMax float64 `json:"defaultHealthMax,omitempty"` - LastModified string `json:"lastModified,omitempty"` - Status *ResponseStatus `json:"status,omitempty"` - MapUpdateInterval int `json:"mapUpdateInterval,omitempty"` - MaxProperties int `json:"maxProperties,omitempty"` - MaxResources int `json:"maxResources,omitempty"` - DefaultSSLClientPrivateKey string `json:"defaultSslClientPrivateKey,omitempty"` - DefaultErrorPenalty int `json:"defaultErrorPenalty,omitempty"` - Links []*Link `json:"links,omitempty"` - Properties []*Property `json:"properties,omitempty"` - MaxTestTimeout float64 `json:"maxTestTimeout,omitempty"` - CNameCoalescingEnabled bool `json:"cnameCoalescingEnabled"` - DefaultHealthMultiplier float64 `json:"defaultHealthMultiplier,omitempty"` - ServermonitorPool string `json:"servermonitorPool,omitempty"` - LoadFeedback bool `json:"loadFeedback"` - MinTTL int64 `json:"minTTL,omitempty"` - GeographicMaps []*GeoMap `json:"geographicMaps,omitempty"` - CIDRMaps []*CIDRMap `json:"cidrMaps,omitempty"` - DefaultMaxUnreachablePenalty int `json:"defaultMaxUnreachablePenalty"` - DefaultHealthThreshold float64 `json:"defaultHealthThreshold,omitempty"` - LastModifiedBy string `json:"lastModifiedBy,omitempty"` - ModificationComments string `json:"modificationComments,omitempty"` - MinTestInterval int `json:"minTestInterval,omitempty"` - PingPacketSize int `json:"pingPacketSize,omitempty"` - DefaultSSLClientCertificate string `json:"defaultSslClientCertificate,omitempty"` - EndUserMappingEnabled bool `json:"endUserMappingEnabled"` - SignAndServe bool `json:"signAndServe"` - SignAndServeAlgorithm *string `json:"signAndServeAlgorithm"` +// Validate validates UpdateDomainRequest +func (r UpdateDomainRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Domain": validation.Validate(r.Domain, validation.Required), + }) +} + +// Validate validates CreateDomainRequest +func (r CreateDomainRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Domain": validation.Validate(r.Domain, validation.Required), + }) } -// DomainsList contains a list of domain items -type DomainsList struct { - DomainItems []*DomainItem `json:"items"` +// Validate validates GetDomainStatusRequest +func (r GetDomainStatusRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// DomainItem is a DomainsList item -type DomainItem struct { - AcgID string `json:"acgId"` - LastModified string `json:"lastModified"` - Links []*Link `json:"links"` - Name string `json:"name"` - Status string `json:"status"` - LastModifiedBy string `json:"lastModifiedBy"` - ChangeID string `json:"changeId"` - ActivationState string `json:"activationState"` - ModificationComments string `json:"modificationComments"` - SignAndServe bool `json:"signAndServe"` - SignAndServeAlgorithm string `json:"signAndServeAlgorithm"` - DeleteRequestID string `json:"deleteRequestId"` +// Validate validates GetDomainRequest +func (r GetDomainRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } // Validate validates Domain @@ -120,18 +201,22 @@ func (d *Domain) Validate() error { return nil } -func (g *gtm) GetDomainStatus(ctx context.Context, domainName string) (*ResponseStatus, error) { +func (g *gtm) GetDomainStatus(ctx context.Context, params GetDomainStatusRequest) (*GetDomainStatusResponse, error) { logger := g.Log(ctx) logger.Debug("GetDomainStatus") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/status/current", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetDomainStatus, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/status/current", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetDomain request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseStatus + var result GetDomainStatusResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetDomain request failed: %w", err) @@ -144,7 +229,7 @@ func (g *gtm) GetDomainStatus(ctx context.Context, domainName string) (*Response return &result, nil } -func (g *gtm) ListDomains(ctx context.Context) ([]*DomainItem, error) { +func (g *gtm) ListDomains(ctx context.Context) ([]DomainItem, error) { logger := g.Log(ctx) logger.Debug("ListDomains") @@ -168,18 +253,22 @@ func (g *gtm) ListDomains(ctx context.Context) ([]*DomainItem, error) { return result.DomainItems, nil } -func (g *gtm) GetDomain(ctx context.Context, domainName string) (*Domain, error) { +func (g *gtm) GetDomain(ctx context.Context, params GetDomainRequest) (*GetDomainResponse, error) { logger := g.Log(ctx) logger.Debug("GetDomain") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetDomain, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetDomain request: %w", err) } setVersionHeader(req, schemaVersion) - var result Domain + var result GetDomainResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetDomain request failed: %w", err) @@ -192,86 +281,106 @@ func (g *gtm) GetDomain(ctx context.Context, domainName string) (*Domain, error) return &result, nil } -// save method; Create or Update -func (d *Domain) save(_ context.Context, g *gtm, queryArgs map[string]string, req *http.Request) (*DomainResponse, error) { +func (g *gtm) CreateDomain(ctx context.Context, params CreateDomainRequest) (*CreateDomainResponse, error) { + logger := g.Log(ctx) + logger.Debug("CreateDomain") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateDomain, ErrStructValidation, err) + } + + postURL := fmt.Sprintf("/config-gtm/v1/domains") + req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create CreateDomain request: %w", err) + } + // set schema version setVersionHeader(req, schemaVersion) // Look for optional args - if len(queryArgs) > 0 { + if params.QueryArgs != nil { q := req.URL.Query() - if val, ok := queryArgs["contractId"]; ok { - q.Add("contractId", strings.TrimPrefix(val, "ctr_")) + if params.QueryArgs.ContractID != "" { + q.Add("contractId", params.QueryArgs.ContractID) } - if val, ok := queryArgs["gid"]; ok { - q.Add("gid", strings.TrimPrefix(val, "grp_")) + if params.QueryArgs.GroupID != "" { + q.Add("gid", params.QueryArgs.GroupID) } req.URL.RawQuery = q.Encode() } - var result DomainResponse - resp, err := g.Exec(req, &result, d) + var result CreateDomainResponse + resp, err := g.Exec(req, &result, params.Domain) if err != nil { return nil, fmt.Errorf("domain request failed: %w", err) } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusCreated { return nil, g.Error(resp) } return &result, nil } -func (g *gtm) CreateDomain(ctx context.Context, domain *Domain, queryArgs map[string]string) (*DomainResponse, error) { +func (g *gtm) UpdateDomain(ctx context.Context, params UpdateDomainRequest) (*UpdateDomainResponse, error) { logger := g.Log(ctx) - logger.Debug("CreateDomain") + logger.Debug("UpdateDomain") - if err := domain.Validate(); err != nil { - return nil, fmt.Errorf("CreateDomain validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateDomain, ErrStructValidation, err) } - postURL := fmt.Sprintf("/config-gtm/v1/domains/") - req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, nil) + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s", params.Domain.Name) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { - return nil, fmt.Errorf("failed to create CreateDomain request: %w", err) + return nil, fmt.Errorf("failed to create UpdateDomain request: %w", err) } - return domain.save(ctx, g, queryArgs, req) -} - -func (g *gtm) UpdateDomain(ctx context.Context, domain *Domain, queryArgs map[string]string) (*ResponseStatus, error) { - logger := g.Log(ctx) - logger.Debug("UpdateDomain") + // set schema version + setVersionHeader(req, schemaVersion) - if err := domain.Validate(); err != nil { - return nil, fmt.Errorf("UpdateDomain validation failed. %w", err) + // Look for optional args + if params.QueryArgs != nil { + q := req.URL.Query() + if params.QueryArgs.ContractID != "" { + q.Add("contractId", params.QueryArgs.ContractID) + } + if params.QueryArgs.GroupID != "" { + q.Add("gid", params.QueryArgs.GroupID) + } + req.URL.RawQuery = q.Encode() } - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s", domain.Name) - req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) + var result UpdateDomainResponse + resp, err := g.Exec(req, &result, params.Domain) if err != nil { - return nil, fmt.Errorf("failed to create UpdateDomain request: %w", err) + return nil, fmt.Errorf("domain request failed: %w", err) } - stat, err := domain.save(ctx, g, queryArgs, req) - if err != nil { - return nil, err + if resp.StatusCode != http.StatusOK { + return nil, g.Error(resp) } - return stat.Status, err + + return &result, nil } -func (g *gtm) DeleteDomain(ctx context.Context, domain *Domain) (*ResponseStatus, error) { +func (g *gtm) DeleteDomain(ctx context.Context, params DeleteDomainRequest) (*DeleteDomainResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteDomain") - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s", domain.Name) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteDomain, ErrStructValidation, err) + } + + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create DeleteDomain request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseBody + var result DeleteDomainResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("DeleteDomain request failed: %w", err) @@ -281,7 +390,7 @@ func (g *gtm) DeleteDomain(ctx context.Context, domain *Domain) (*ResponseStatus return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } // NullPerObjectAttributeStruct represents core and child null object attributes diff --git a/pkg/gtm/domain_test.go b/pkg/gtm/domain_test.go index ec0f8ef6..ae77f098 100644 --- a/pkg/gtm/domain_test.go +++ b/pkg/gtm/domain_test.go @@ -10,7 +10,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -20,7 +20,7 @@ func TestGTM_ListDomains(t *testing.T) { responseStatus int responseBody string expectedPath string - expectedResponse []*DomainItem + expectedResponse []DomainItem withError error headers http.Header }{ @@ -65,7 +65,7 @@ func TestGTM_ListDomains(t *testing.T) { }] }]}`, expectedPath: "/config-gtm/v1/domains", - expectedResponse: []*DomainItem{{ + expectedResponse: []DomainItem{{ AcgID: "1-2345", LastModified: "2014-03-03T16:02:45.000+0000", Name: "example.akadns.net", @@ -77,7 +77,7 @@ func TestGTM_ListDomains(t *testing.T) { SignAndServe: false, SignAndServeAlgorithm: "", DeleteRequestID: "", - Links: []*Link{{ + Links: []Link{{ Rel: "self", Href: "/config-gtm/v1/domains/example.akadns.net", }}, @@ -94,7 +94,7 @@ func TestGTM_ListDomains(t *testing.T) { SignAndServe: false, SignAndServeAlgorithm: "", DeleteRequestID: "", - Links: []*Link{{ + Links: []Link{{ Rel: "self", Href: "/config-gtm/v1/domains/example.akadns.net", }}, @@ -261,7 +261,7 @@ func TestGTM_NullFieldMap(t *testing.T) { } func TestGTM_GetDomain(t *testing.T) { - var result Domain + var result GetDomainResponse respData, err := loadTestData("TestGTM_GetDomain.resp.json") if err != nil { @@ -273,22 +273,26 @@ func TestGTM_GetDomain(t *testing.T) { } tests := map[string]struct { - domain string + params GetDomainRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *Domain + expectedResponse *GetDomainResponse withError error }{ "200 OK": { - domain: "example.akadns.net", + params: GetDomainRequest{ + DomainName: "example.akadns.net", + }, responseStatus: http.StatusOK, responseBody: respData, expectedPath: "/config-gtm/v1/domains/example.akadns.net", expectedResponse: &result, }, "500 internal server error": { - domain: "example.akadns.net", + params: GetDomainRequest{ + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -316,7 +320,7 @@ func TestGTM_GetDomain(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetDomain(context.Background(), test.domain) + result, err := client.GetDomain(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -328,7 +332,7 @@ func TestGTM_GetDomain(t *testing.T) { } func TestGTM_CreateDomain(t *testing.T) { - var result DomainResponse + var result CreateDomainResponse respData, err := loadTestData("TestGTM_GetDomain.resp.json") if err != nil { @@ -340,21 +344,22 @@ func TestGTM_CreateDomain(t *testing.T) { } tests := map[string]struct { - domain Domain - query map[string]string + params CreateDomainRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *DomainResponse + expectedResponse *CreateDomainResponse withError error headers http.Header }{ "201 Created": { - domain: Domain{ - Name: "gtmdomtest.akadns.net", - Type: "basic", + params: CreateDomainRequest{ + Domain: &Domain{ + Name: "gtmdomtest.akadns.net", + Type: "basic", + }, + QueryArgs: &DomainQueryArgs{ContractID: "1-2ABCDE"}, }, - query: map[string]string{"contractId": "1-2ABCDE"}, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -364,11 +369,13 @@ func TestGTM_CreateDomain(t *testing.T) { expectedPath: "/config-gtm/v1/domains?contractId=1-2ABCDE", }, "500 internal server error": { - domain: Domain{ - Name: "gtmdomtest.akadns.net", - Type: "basic", + params: CreateDomainRequest{ + Domain: &Domain{ + Name: "gtmdomtest.akadns.net", + Type: "basic", + }, + QueryArgs: &DomainQueryArgs{ContractID: "1-2ABCDE"}, }, - query: map[string]string{"contractId": "1-2ABCDE"}, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -389,6 +396,7 @@ func TestGTM_CreateDomain(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -400,7 +408,7 @@ func TestGTM_CreateDomain(t *testing.T) { result, err := client.CreateDomain( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), &test.domain, test.query) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -412,7 +420,7 @@ func TestGTM_CreateDomain(t *testing.T) { } func TestGTM_UpdateDomain(t *testing.T) { - var result DomainResponse + var result UpdateDomainResponse respData, err := loadTestData("TestGTM_UpdateDomain.resp.json") if err != nil { @@ -424,36 +432,40 @@ func TestGTM_UpdateDomain(t *testing.T) { } tests := map[string]struct { - domain Domain - query map[string]string + params UpdateDomainRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdateDomainResponse withError error headers http.Header }{ "200 Success": { - domain: Domain{ - EndUserMappingEnabled: false, - Name: "gtmdomtest.akadns.net", - Type: "basic", + params: UpdateDomainRequest{ + Domain: &Domain{ + EndUserMappingEnabled: false, + Name: "gtmdomtest.akadns.net", + Type: "basic", + }, + QueryArgs: &DomainQueryArgs{ContractID: "1-2ABCDE"}, }, - query: map[string]string{"contractId": "1-2ABCDE"}, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, - responseStatus: http.StatusCreated, + responseStatus: http.StatusOK, responseBody: respData, - expectedResponse: result.Status, - expectedPath: "/config-gtm/v1/domains?contractId=1-2ABCDE", + expectedResponse: &result, + expectedPath: "/config-gtm/v1/domains/gtmdomtest.akadns.net?contractId=1-2ABCDE", }, "500 internal server error": { - domain: Domain{ - Name: "gtmdomtest.akadns.net", - Type: "basic", + params: UpdateDomainRequest{ + Domain: &Domain{ + EndUserMappingEnabled: false, + Name: "gtmdomtest.akadns.net", + Type: "basic", + }, + QueryArgs: &DomainQueryArgs{ContractID: "1-2ABCDE"}, }, - query: map[string]string{"contractId": "1-2ABCDE"}, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -461,7 +473,7 @@ func TestGTM_UpdateDomain(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating zone" }`), - expectedPath: "/config-gtm/v1/domains?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/gtmdomtest.akadns.net?contractId=1-2ABCDE", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -474,6 +486,7 @@ func TestGTM_UpdateDomain(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -485,7 +498,7 @@ func TestGTM_UpdateDomain(t *testing.T) { result, err := client.UpdateDomain( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), &test.domain, test.query) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/errors.go b/pkg/gtm/errors.go index 0f956b6d..1271e20f 100644 --- a/pkg/gtm/errors.go +++ b/pkg/gtm/errors.go @@ -6,13 +6,17 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) var ( // ErrNotFound used when status code is 404 Not Found ErrNotFound = errors.New("404 Not Found") + + // ErrNoDatacenterAssignedToMap occurs when no datacenter is assigned to the map target during the creation of a geographic property. + ErrNoDatacenterAssignedToMap = errors.New("no datacenter is assigned to map target (all others)") ) type ( @@ -70,6 +74,10 @@ func (e *Error) Is(target error) bool { return true } + if errors.Is(target, ErrNoDatacenterAssignedToMap) && strings.Contains(e.Detail, "no datacenter is assigned to map target (all others)") { + return true + } + var t *Error if !errors.As(target, &t) { return false diff --git a/pkg/gtm/errors_test.go b/pkg/gtm/errors_test.go index 354b2f97..2c60a6b5 100644 --- a/pkg/gtm/errors_test.go +++ b/pkg/gtm/errors_test.go @@ -7,9 +7,8 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" - "github.com/tj/assert" ) @@ -107,3 +106,25 @@ func TestJSONErrorUnmarshalling(t *testing.T) { }) } } + +func TestIs(t *testing.T) { + tests := map[string]struct { + err Error + target Error + expected bool + }{ + "no datacenter is assigned to map target (all others)": { + err: Error{StatusCode: 400, Type: "https://problems.luna.akamaiapis.net/config-gtm/v1/propertyValidationError", Title: "Property Validation Error", Detail: "Invalid configuration for property \"publishprod\": no datacenter is assigned to map target (all others)", + Errors: nil}, + target: Error{StatusCode: 400, Type: "https://problems.luna.akamaiapis.net/config-gtm/v1/propertyValidationError", Title: "Property Validation Error", Detail: "Invalid configuration for property \"publishprod\": no datacenter is assigned to map target (all others)", + Errors: nil}, + expected: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, test.err.Is(&test.target), test.expected) + }) + } +} diff --git a/pkg/gtm/geomap.go b/pkg/gtm/geomap.go index 5a776efa..2a1289f4 100644 --- a/pkg/gtm/geomap.go +++ b/pkg/gtm/geomap.go @@ -2,70 +2,154 @@ package gtm import ( "context" + "errors" "fmt" "net/http" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // GeoAssignment represents a GTM Geo assignment element + GeoAssignment struct { + DatacenterBase + Countries []string `json:"countries"` + } + + // GeoMap represents a GTM GeoMap + GeoMap struct { + DefaultDatacenter *DatacenterBase `json:"defaultDatacenter"` + Assignments []GeoAssignment `json:"assignments,omitempty"` + Name string `json:"name"` + Links []Link `json:"links,omitempty"` + } + + // GeoMapList represents the returned GTM GeoMap List body + GeoMapList struct { + GeoMapItems []GeoMap `json:"items"` + } + + // ListGeoMapsRequest contains request parameters for ListGeoMaps + ListGeoMapsRequest struct { + DomainName string + } + + // GetGeoMapRequest contains request parameters for GetGeoMap + GetGeoMapRequest struct { + MapName string + DomainName string + } + + // GetGeoMapResponse contains the response data from GetGeoMap operation + GetGeoMapResponse GeoMap + + // GeoMapRequest contains request parameters + GeoMapRequest struct { + GeoMap *GeoMap + DomainName string + } + + // CreateGeoMapRequest contains request parameters for CreateGeoMap + CreateGeoMapRequest GeoMapRequest + + // CreateGeoMapResponse contains the response data from CreateGeoMap operation + CreateGeoMapResponse struct { + Resource *GeoMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // UpdateGeoMapRequest contains request parameters for UpdateGeoMap + UpdateGeoMapRequest GeoMapRequest + + // UpdateGeoMapResponse contains the response data from UpdateGeoMap operation + UpdateGeoMapResponse struct { + Resource *GeoMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // DeleteGeoMapRequest contains request parameters for DeleteGeoMap + DeleteGeoMapRequest struct { + MapName string + DomainName string + } + + // DeleteGeoMapResponse contains the response data from DeleteGeoMap operation + DeleteGeoMapResponse struct { + Resource *GeoMap `json:"resource"` + Status *ResponseStatus `json:"status"` + } ) -// GeoMaps contains operations available on a GeoMap resource. -type GeoMaps interface { - // ListGeoMaps retrieves all GeoMaps. - // - // See: https://techdocs.akamai.com/gtm/reference/get-geographic-maps - ListGeoMaps(context.Context, string) ([]*GeoMap, error) - // GetGeoMap retrieves a GeoMap with the given name. - // - // See: https://techdocs.akamai.com/gtm/reference/get-geographic-map - GetGeoMap(context.Context, string, string) (*GeoMap, error) - // CreateGeoMap creates the datacenter identified by the receiver argument in the specified domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-geographic-map - CreateGeoMap(context.Context, *GeoMap, string) (*GeoMapResponse, error) - // DeleteGeoMap deletes the datacenter identified by the receiver argument from the domain specified. - // - // See: https://techdocs.akamai.com/gtm/reference/delete-geographic-map - DeleteGeoMap(context.Context, *GeoMap, string) (*ResponseStatus, error) - // UpdateGeoMap updates the datacenter identified in the receiver argument in the provided domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-geographic-map - UpdateGeoMap(context.Context, *GeoMap, string) (*ResponseStatus, error) +var ( + // ErrListGeoMaps is returned when ListGeoMaps fails + ErrListGeoMaps = errors.New("list geomaps") + // ErrGetGeoMap is returned when GetGeoMap fails + ErrGetGeoMap = errors.New("get geomap") + // ErrCreateGeoMap is returned when CreateGeoMap fails + ErrCreateGeoMap = errors.New("create geomap") + // ErrUpdateGeoMap is returned when UpdateGeoMap fails + ErrUpdateGeoMap = errors.New("update geomap") + // ErrDeleteGeoMap is returned when DeleteGeoMap fails + ErrDeleteGeoMap = errors.New("delete geomap") +) + +// Validate validates ListGeoMapsRequest +func (r ListGeoMapsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// GeoAssignment represents a GTM Geo assignment element -type GeoAssignment struct { - DatacenterBase - Countries []string `json:"countries"` +// Validate validates GetGeoMapRequest +func (r GetGeoMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "MapName": validation.Validate(r.MapName, validation.Required), + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// GeoMap represents a GTM GeoMap -type GeoMap struct { - DefaultDatacenter *DatacenterBase `json:"defaultDatacenter"` - Assignments []*GeoAssignment `json:"assignments,omitempty"` - Name string `json:"name"` - Links []*Link `json:"links,omitempty"` +// Validate validates CreateGeoMapRequest +func (r CreateGeoMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "GeoMap": validation.Validate(r.GeoMap, validation.Required), + }) } -// GeoMapList represents the returned GTM GeoMap List body -type GeoMapList struct { - GeoMapItems []*GeoMap `json:"items"` +// Validate validates UpdateGeoMapRequest +func (r UpdateGeoMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "GeoMap": validation.Validate(r.GeoMap, validation.Required), + }) } -// Validate validates GeoMap -func (m *GeoMap) Validate() error { - if len(m.Name) < 1 { - return fmt.Errorf("GeoMap is missing Name") - } - if m.DefaultDatacenter == nil { - return fmt.Errorf("GeoMap is missing DefaultDatacenter") - } +// Validate validates DeleteGeoMapRequest +func (r DeleteGeoMapRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "MapName": validation.Validate(r.MapName, validation.Required), + }) +} - return nil +// Validate validates GeoMap +func (g *GeoMap) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Name": validation.Validate(g.Name, validation.Required), + "DefaultDatacenter": validation.Validate(g.DefaultDatacenter, validation.Required), + }) } -func (g *gtm) ListGeoMaps(ctx context.Context, domainName string) ([]*GeoMap, error) { +func (g *gtm) ListGeoMaps(ctx context.Context, params ListGeoMapsRequest) ([]GeoMap, error) { logger := g.Log(ctx) logger.Debug("ListGeoMaps") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrListGeoMaps, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListGeoMaps request: %w", err) @@ -85,18 +169,22 @@ func (g *gtm) ListGeoMaps(ctx context.Context, domainName string) ([]*GeoMap, er return result.GeoMapItems, nil } -func (g *gtm) GetGeoMap(ctx context.Context, mapName, domainName string) (*GeoMap, error) { +func (g *gtm) GetGeoMap(ctx context.Context, params GetGeoMapRequest) (*GetGeoMapResponse, error) { logger := g.Log(ctx) logger.Debug("GetGeoMap") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", domainName, mapName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetGeoMap, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", params.DomainName, params.MapName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetGeoMap request: %w", err) } setVersionHeader(req, schemaVersion) - var result GeoMap + var result GetGeoMapResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetGeoMap request failed: %w", err) @@ -109,67 +197,78 @@ func (g *gtm) GetGeoMap(ctx context.Context, mapName, domainName string) (*GeoMa return &result, nil } -func (g *gtm) CreateGeoMap(ctx context.Context, geo *GeoMap, domainName string) (*GeoMapResponse, error) { +func (g *gtm) CreateGeoMap(ctx context.Context, params CreateGeoMapRequest) (*CreateGeoMapResponse, error) { logger := g.Log(ctx) logger.Debug("CreateGeoMap") - return geo.save(ctx, g, domainName) -} + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateGeoMap, ErrStructValidation, err) + } -func (g *gtm) UpdateGeoMap(ctx context.Context, geo *GeoMap, domainName string) (*ResponseStatus, error) { - logger := g.Log(ctx) - logger.Debug("UpdateGeoMap") + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", params.DomainName, params.GeoMap.Name) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create GeoMap request: %w", err) + } + setVersionHeader(req, schemaVersion) - stat, err := geo.save(ctx, g, domainName) + var result CreateGeoMapResponse + resp, err := g.Exec(req, &result, params.GeoMap) if err != nil { - return nil, err + return nil, fmt.Errorf("GeoMap request failed: %w", err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, g.Error(resp) } - return stat.Status, err + + return &result, nil } -// Save GeoMap in given domain. Common path for Create and Update. -func (m *GeoMap) save(ctx context.Context, g *gtm, domainName string) (*GeoMapResponse, error) { - if err := m.Validate(); err != nil { - return nil, fmt.Errorf("GeoMap validation failed. %w", err) +func (g *gtm) UpdateGeoMap(ctx context.Context, params UpdateGeoMapRequest) (*UpdateGeoMapResponse, error) { + logger := g.Log(ctx) + logger.Debug("UpdateGeoMap") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateGeoMap, ErrStructValidation, err) } - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", domainName, m.Name) + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", params.DomainName, params.GeoMap.Name) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GeoMap request: %w", err) } setVersionHeader(req, schemaVersion) - var result GeoMapResponse - resp, err := g.Exec(req, &result, m) + var result UpdateGeoMapResponse + resp, err := g.Exec(req, &result, params.GeoMap) if err != nil { return nil, fmt.Errorf("GeoMap request failed: %w", err) } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusOK { return nil, g.Error(resp) } return &result, nil } -func (g *gtm) DeleteGeoMap(ctx context.Context, geo *GeoMap, domainName string) (*ResponseStatus, error) { +func (g *gtm) DeleteGeoMap(ctx context.Context, params DeleteGeoMapRequest) (*DeleteGeoMapResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteGeoMap") - if err := geo.Validate(); err != nil { - logger.Errorf("Resource validation failed. %w", err) - return nil, fmt.Errorf("GeoMap validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteGeoMap, ErrStructValidation, err) } - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", domainName, geo.Name) + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/geographic-maps/%s", params.DomainName, params.MapName) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Delete request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseBody + var result DeleteGeoMapResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GeoMap request failed: %w", err) @@ -179,5 +278,5 @@ func (g *gtm) DeleteGeoMap(ctx context.Context, geo *GeoMap, domainName string) return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } diff --git a/pkg/gtm/geomap_test.go b/pkg/gtm/geomap_test.go index 6c00645b..4b155a2d 100644 --- a/pkg/gtm/geomap_test.go +++ b/pkg/gtm/geomap_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,16 +27,18 @@ func TestGTM_ListGeoMap(t *testing.T) { } tests := map[string]struct { - domainName string + params ListGeoMapsRequest responseStatus int responseBody string expectedPath string - expectedResponse []*GeoMap + expectedResponse []GeoMap withError error headers http.Header }{ "200 OK": { - domainName: "example.akadns.net", + params: ListGeoMapsRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -46,7 +48,9 @@ func TestGTM_ListGeoMap(t *testing.T) { expectedResponse: result.GeoMapItems, }, "500 internal server error": { - domainName: "example.akadns.net", + params: ListGeoMapsRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -79,7 +83,7 @@ func TestGTM_ListGeoMap(t *testing.T) { result, err := client.ListGeoMaps( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -91,7 +95,7 @@ func TestGTM_ListGeoMap(t *testing.T) { } func TestGTM_GetGeoMap(t *testing.T) { - var result GeoMap + var result GetGeoMapResponse respData, err := loadTestData("TestGTM_GetGeoMap.resp.json") if err != nil { @@ -103,18 +107,19 @@ func TestGTM_GetGeoMap(t *testing.T) { } tests := map[string]struct { - name string - domainName string + params GetGeoMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *GeoMap + expectedResponse *GetGeoMapResponse withError error headers http.Header }{ "200 OK": { - name: "Software-rollout", - domainName: "example.akadns.net", + params: GetGeoMapRequest{ + MapName: "Software-rollout", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -124,8 +129,10 @@ func TestGTM_GetGeoMap(t *testing.T) { expectedResponse: &result, }, "500 internal server error": { - name: "Software-rollout", - domainName: "example.akadns.net", + params: GetGeoMapRequest{ + MapName: "Software-rollout", + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -158,7 +165,7 @@ func TestGTM_GetGeoMap(t *testing.T) { result, err := client.GetGeoMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.name, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -170,7 +177,7 @@ func TestGTM_GetGeoMap(t *testing.T) { } func TestGTM_CreateGeoMap(t *testing.T) { - var result GeoMapResponse + var result CreateGeoMapResponse var req GeoMap respData, err := loadTestData("TestGTM_CreateGeoMap.resp.json") @@ -192,29 +199,32 @@ func TestGTM_CreateGeoMap(t *testing.T) { } tests := map[string]struct { - GeoMap *GeoMap - domainName string + params CreateGeoMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *GeoMapResponse + expectedResponse *CreateGeoMapResponse withError error headers http.Header }{ "200 OK": { - GeoMap: &req, - domainName: "example.akadns.net", + params: CreateGeoMapRequest{ + GeoMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, - responseStatus: http.StatusOK, + responseStatus: http.StatusCreated, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/geographic-maps/UK%20Delivery", expectedResponse: &result, }, "500 internal server error": { - GeoMap: &req, - domainName: "example.akadns.net", + params: CreateGeoMapRequest{ + GeoMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -246,7 +256,7 @@ func TestGTM_CreateGeoMap(t *testing.T) { result, err := client.CreateGeoMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.GeoMap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -258,7 +268,7 @@ func TestGTM_CreateGeoMap(t *testing.T) { } func TestGTM_UpdateGeoMap(t *testing.T) { - var result GeoMapResponse + var result UpdateGeoMapResponse var req GeoMap respData, err := loadTestData("TestGTM_CreateGeoMap.resp.json") @@ -280,29 +290,32 @@ func TestGTM_UpdateGeoMap(t *testing.T) { } tests := map[string]struct { - GeoMap *GeoMap - domainName string + params UpdateGeoMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdateGeoMapResponse withError error headers http.Header }{ "200 OK": { - GeoMap: &req, - domainName: "example.akadns.net", + params: UpdateGeoMapRequest{ + GeoMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/geographic-maps/UK%20Delivery", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - GeoMap: &req, - domainName: "example.akadns.net", + params: UpdateGeoMapRequest{ + GeoMap: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -334,7 +347,7 @@ func TestGTM_UpdateGeoMap(t *testing.T) { result, err := client.UpdateGeoMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.GeoMap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -346,7 +359,7 @@ func TestGTM_UpdateGeoMap(t *testing.T) { } func TestGTM_DeleteGeoMap(t *testing.T) { - var result GeoMapResponse + var result DeleteGeoMapResponse var req GeoMap respData, err := loadTestData("TestGTM_CreateGeoMap.resp.json") @@ -368,29 +381,32 @@ func TestGTM_DeleteGeoMap(t *testing.T) { } tests := map[string]struct { - GeoMap *GeoMap - domainName string + params DeleteGeoMapRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *DeleteGeoMapResponse withError error headers http.Header }{ "200 OK": { - GeoMap: &req, - domainName: "example.akadns.net", + params: DeleteGeoMapRequest{ + MapName: "UK%20Delivery", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: string(respData), expectedPath: "/config-gtm/v1/domains/example.akadns.net/geographic-maps/UK%20Delivery", - expectedResponse: result.Status, + expectedResponse: &result, }, "500 internal server error": { - GeoMap: &req, - domainName: "example.akadns.net", + params: DeleteGeoMapRequest{ + MapName: "UK%20Delivery", + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -422,7 +438,7 @@ func TestGTM_DeleteGeoMap(t *testing.T) { result, err := client.DeleteGeoMap( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.GeoMap, test.domainName) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/gtm.go b/pkg/gtm/gtm.go index 5903788c..62623a31 100644 --- a/pkg/gtm/gtm.go +++ b/pkg/gtm/gtm.go @@ -4,21 +4,226 @@ package gtm import ( + "context" + "errors" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" +) + +var ( + // ErrStructValidation is returned when given struct validation failed. + ErrStructValidation = errors.New("struct validation") ) type ( // GTM is the gtm api interface GTM interface { - Domains - Properties - Datacenters - Resources - ASMaps - GeoMaps - CIDRMaps + // Domains + + // NullFieldMap retrieves map of null fields. + NullFieldMap(context.Context, *Domain) (*NullFieldMapStruct, error) + + // GetDomainStatus retrieves current status for the given domain name. + // + // See: https://techdocs.akamai.com/gtm/reference/get-status-current + GetDomainStatus(context.Context, GetDomainStatusRequest) (*GetDomainStatusResponse, error) + + // ListDomains retrieves all Domains. + // + // See: https://techdocs.akamai.com/gtm/reference/get-domains + ListDomains(context.Context) ([]DomainItem, error) + + // GetDomain retrieves a Domain with the given domain name. + // + // See: https://techdocs.akamai.com/gtm/reference/get-domain + GetDomain(context.Context, GetDomainRequest) (*GetDomainResponse, error) + + // CreateDomain creates domain. + // + // See: https://techdocs.akamai.com/gtm/reference/post-domain + CreateDomain(context.Context, CreateDomainRequest) (*CreateDomainResponse, error) + + // DeleteDomain is a method applied to a domain object resulting in removal. + // + // See: ** Not Supported by API ** + DeleteDomain(context.Context, DeleteDomainRequest) (*DeleteDomainResponse, error) + + // UpdateDomain is a method applied to a domain object resulting in an update. + // + // See: https://techdocs.akamai.com/gtm/reference/put-domain + UpdateDomain(context.Context, UpdateDomainRequest) (*UpdateDomainResponse, error) + + // Properties + + // ListProperties retrieves all Properties for the provided domainName. + // + // See: https://techdocs.akamai.com/gtm/reference/get-properties + ListProperties(context.Context, ListPropertiesRequest) ([]Property, error) + + // GetProperty retrieves a Property with the given domain and property names. + // + // See: https://techdocs.akamai.com/gtm/reference/get-property + GetProperty(context.Context, GetPropertyRequest) (*GetPropertyResponse, error) + + // CreateProperty creates property. + // + // See: https://techdocs.akamai.com/gtm/reference/put-property + CreateProperty(context.Context, CreatePropertyRequest) (*CreatePropertyResponse, error) + + // DeleteProperty is a method applied to a property object resulting in removal. + // + // See: https://techdocs.akamai.com/gtm/reference/delete-property + DeleteProperty(context.Context, DeletePropertyRequest) (*DeletePropertyResponse, error) + + // UpdateProperty is a method applied to a property object resulting in an update. + // + // See: https://techdocs.akamai.com/gtm/reference/put-property + UpdateProperty(context.Context, UpdatePropertyRequest) (*UpdatePropertyResponse, error) + + // Datacenters + + // ListDatacenters retrieves all Datacenters. + // + // See: https://techdocs.akamai.com/gtm/reference/get-datacenters + ListDatacenters(context.Context, ListDatacentersRequest) ([]Datacenter, error) + + // GetDatacenter retrieves a Datacenter with the given name. NOTE: Id arg is int! + // + // See: https://techdocs.akamai.com/gtm/reference/get-datacenter + GetDatacenter(context.Context, GetDatacenterRequest) (*Datacenter, error) + + // CreateDatacenter creates the datacenter identified by the receiver argument in the specified domain. + // + // See: https://techdocs.akamai.com/gtm/reference/post-datacenter + CreateDatacenter(context.Context, CreateDatacenterRequest) (*CreateDatacenterResponse, error) + + // DeleteDatacenter deletes the datacenter identified by the receiver argument from the domain specified. + // + // See: https://techdocs.akamai.com/gtm/reference/delete-datacenter + DeleteDatacenter(context.Context, DeleteDatacenterRequest) (*DeleteDatacenterResponse, error) + + // UpdateDatacenter updates the datacenter identified in the receiver argument in the provided domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-datacenter + UpdateDatacenter(context.Context, UpdateDatacenterRequest) (*UpdateDatacenterResponse, error) + + // CreateMapsDefaultDatacenter creates Default Datacenter for Maps. + CreateMapsDefaultDatacenter(context.Context, string) (*Datacenter, error) + + // CreateIPv4DefaultDatacenter creates Default Datacenter for IPv4 Selector. + CreateIPv4DefaultDatacenter(context.Context, string) (*Datacenter, error) + + // CreateIPv6DefaultDatacenter creates Default Datacenter for IPv6 Selector. + CreateIPv6DefaultDatacenter(context.Context, string) (*Datacenter, error) + + // Resources + + // ListResources retrieves all Resources + // + // See: https://techdocs.akamai.com/gtm/reference/get-resources + ListResources(context.Context, ListResourcesRequest) ([]Resource, error) + + // GetResource retrieves a Resource with the given name. + // + // See: https://techdocs.akamai.com/gtm/reference/get-resource + GetResource(context.Context, GetResourceRequest) (*GetResourceResponse, error) + + // CreateResource creates the datacenter identified by the receiver argument in the specified domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-resource + CreateResource(context.Context, CreateResourceRequest) (*CreateResourceResponse, error) + + // DeleteResource deletes the datacenter identified by the receiver argument from the domain specified. + // + // See: https://techdocs.akamai.com/gtm/reference/delete-resource + DeleteResource(context.Context, DeleteResourceRequest) (*DeleteResourceResponse, error) + + // UpdateResource updates the datacenter identified in the receiver argument in the provided domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-resource + UpdateResource(context.Context, UpdateResourceRequest) (*UpdateResourceResponse, error) + + // ASMaps + + // ListASMaps retrieves all AsMaps. + // + // See: https://techdocs.akamai.com/gtm/reference/get-as-maps + ListASMaps(context.Context, ListASMapsRequest) ([]ASMap, error) + + // GetASMap retrieves a AsMap with the given name. + // + // See: https://techdocs.akamai.com/gtm/reference/get-as-map + GetASMap(context.Context, GetASMapRequest) (*GetASMapResponse, error) + + // CreateASMap creates the datacenter identified by the receiver argument in the specified domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-as-map + CreateASMap(context.Context, CreateASMapRequest) (*CreateASMapResponse, error) + + // DeleteASMap deletes the datacenter identified by the receiver argument from the domain specified. + // + // See: https://techdocs.akamai.com/gtm/reference/delete-as-map + DeleteASMap(context.Context, DeleteASMapRequest) (*DeleteASMapResponse, error) + + // UpdateASMap updates the datacenter identified in the receiver argument in the provided domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-as-map + UpdateASMap(context.Context, UpdateASMapRequest) (*UpdateASMapResponse, error) + + // GeoMaps + + // ListGeoMaps retrieves all GeoMaps. + // + // See: https://techdocs.akamai.com/gtm/reference/get-geographic-maps + ListGeoMaps(context.Context, ListGeoMapsRequest) ([]GeoMap, error) + + // GetGeoMap retrieves a GeoMap with the given name. + // + // See: https://techdocs.akamai.com/gtm/reference/get-geographic-map + GetGeoMap(context.Context, GetGeoMapRequest) (*GetGeoMapResponse, error) + + // CreateGeoMap creates the datacenter identified by the receiver argument in the specified domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-geographic-map + CreateGeoMap(context.Context, CreateGeoMapRequest) (*CreateGeoMapResponse, error) + + // DeleteGeoMap deletes the datacenter identified by the receiver argument from the domain specified. + // + // See: https://techdocs.akamai.com/gtm/reference/delete-geographic-map + DeleteGeoMap(context.Context, DeleteGeoMapRequest) (*DeleteGeoMapResponse, error) + + // UpdateGeoMap updates the datacenter identified in the receiver argument in the provided domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-geographic-map + UpdateGeoMap(context.Context, UpdateGeoMapRequest) (*UpdateGeoMapResponse, error) + + // CIDRMaps + + // ListCIDRMaps retrieves all CIDRMaps. + // + // See: https://techdocs.akamai.com/gtm/reference/get-cidr-maps + ListCIDRMaps(context.Context, ListCIDRMapsRequest) ([]CIDRMap, error) + + // GetCIDRMap retrieves a CIDRMap with the given name. + // + // See: https://techdocs.akamai.com/gtm/reference/get-cidr-map + GetCIDRMap(context.Context, GetCIDRMapRequest) (*GetCIDRMapResponse, error) + + // CreateCIDRMap creates the datacenter identified by the receiver argument in the specified domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-cidr-map + CreateCIDRMap(context.Context, CreateCIDRMapRequest) (*CreateCIDRMapResponse, error) + + // DeleteCIDRMap deletes the datacenter identified by the receiver argument from the domain specified. + // + // See: https://techdocs.akamai.com/gtm/reference/delete-cidr-maps + DeleteCIDRMap(context.Context, DeleteCIDRMapRequest) (*DeleteCIDRMapResponse, error) + + // UpdateCIDRMap updates the datacenter identified in the receiver argument in the provided domain. + // + // See: https://techdocs.akamai.com/gtm/reference/put-cidr-map + UpdateCIDRMap(context.Context, UpdateCIDRMapRequest) (*UpdateCIDRMapResponse, error) } gtm struct { diff --git a/pkg/gtm/gtm_test.go b/pkg/gtm/gtm_test.go index 813e7383..5753d5b6 100644 --- a/pkg/gtm/gtm_test.go +++ b/pkg/gtm/gtm_test.go @@ -10,8 +10,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/gtm/mocks.go b/pkg/gtm/mocks.go index c9ecbd66..88756aaf 100644 --- a/pkg/gtm/mocks.go +++ b/pkg/gtm/mocks.go @@ -24,118 +24,118 @@ func (p *Mock) NullFieldMap(ctx context.Context, domain *Domain) (*NullFieldMapS return args.Get(0).(*NullFieldMapStruct), args.Error(1) } -func (p *Mock) GetDomainStatus(ctx context.Context, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, domain) +func (p *Mock) GetDomainStatus(ctx context.Context, req GetDomainStatusRequest) (*GetDomainStatusResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*GetDomainStatusResponse), args.Error(1) } -func (p *Mock) ListDomains(ctx context.Context) ([]*DomainItem, error) { +func (p *Mock) ListDomains(ctx context.Context) ([]DomainItem, error) { args := p.Called(ctx) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*DomainItem), args.Error(1) + return args.Get(0).([]DomainItem), args.Error(1) } -func (p *Mock) GetDomain(ctx context.Context, domain string) (*Domain, error) { - args := p.Called(ctx, domain) +func (p *Mock) GetDomain(ctx context.Context, req GetDomainRequest) (*GetDomainResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*Domain), args.Error(1) + return args.Get(0).(*GetDomainResponse), args.Error(1) } -func (p *Mock) CreateDomain(ctx context.Context, domain *Domain, queryArgs map[string]string) (*DomainResponse, error) { - args := p.Called(ctx, domain, queryArgs) +func (p *Mock) CreateDomain(ctx context.Context, req CreateDomainRequest) (*CreateDomainResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*DomainResponse), args.Error(1) + return args.Get(0).(*CreateDomainResponse), args.Error(1) } -func (p *Mock) DeleteDomain(ctx context.Context, domain *Domain) (*ResponseStatus, error) { - args := p.Called(ctx, domain) +func (p *Mock) DeleteDomain(ctx context.Context, req DeleteDomainRequest) (*DeleteDomainResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeleteDomainResponse), args.Error(1) } -func (p *Mock) UpdateDomain(ctx context.Context, domain *Domain, queryArgs map[string]string) (*ResponseStatus, error) { - args := p.Called(ctx, domain, queryArgs) +func (p *Mock) UpdateDomain(ctx context.Context, req UpdateDomainRequest) (*UpdateDomainResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdateDomainResponse), args.Error(1) } -func (p *Mock) GetProperty(ctx context.Context, prop string, domain string) (*Property, error) { - args := p.Called(ctx, prop, domain) +func (p *Mock) GetProperty(ctx context.Context, req GetPropertyRequest) (*GetPropertyResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*Property), args.Error(1) + return args.Get(0).(*GetPropertyResponse), args.Error(1) } -func (p *Mock) DeleteProperty(ctx context.Context, prop *Property, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, prop, domain) +func (p *Mock) DeleteProperty(ctx context.Context, req DeletePropertyRequest) (*DeletePropertyResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeletePropertyResponse), args.Error(1) } -func (p *Mock) CreateProperty(ctx context.Context, prop *Property, domain string) (*PropertyResponse, error) { - args := p.Called(ctx, prop, domain) +func (p *Mock) CreateProperty(ctx context.Context, req CreatePropertyRequest) (*CreatePropertyResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*PropertyResponse), args.Error(1) + return args.Get(0).(*CreatePropertyResponse), args.Error(1) } -func (p *Mock) UpdateProperty(ctx context.Context, prop *Property, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, prop, domain) +func (p *Mock) UpdateProperty(ctx context.Context, req UpdatePropertyRequest) (*UpdatePropertyResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdatePropertyResponse), args.Error(1) } -func (p *Mock) ListProperties(ctx context.Context, domain string) ([]*Property, error) { - args := p.Called(ctx, domain) +func (p *Mock) ListProperties(ctx context.Context, req ListPropertiesRequest) ([]Property, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*Property), args.Error(1) + return args.Get(0).([]Property), args.Error(1) } -func (p *Mock) GetDatacenter(ctx context.Context, dcID int, domain string) (*Datacenter, error) { - args := p.Called(ctx, dcID, domain) +func (p *Mock) GetDatacenter(ctx context.Context, req GetDatacenterRequest) (*Datacenter, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) @@ -144,44 +144,44 @@ func (p *Mock) GetDatacenter(ctx context.Context, dcID int, domain string) (*Dat return args.Get(0).(*Datacenter), args.Error(1) } -func (p *Mock) CreateDatacenter(ctx context.Context, dc *Datacenter, domain string) (*DatacenterResponse, error) { - args := p.Called(ctx, dc, domain) +func (p *Mock) CreateDatacenter(ctx context.Context, req CreateDatacenterRequest) (*CreateDatacenterResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*DatacenterResponse), args.Error(1) + return args.Get(0).(*CreateDatacenterResponse), args.Error(1) } -func (p *Mock) DeleteDatacenter(ctx context.Context, dc *Datacenter, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, dc, domain) +func (p *Mock) DeleteDatacenter(ctx context.Context, req DeleteDatacenterRequest) (*DeleteDatacenterResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeleteDatacenterResponse), args.Error(1) } -func (p *Mock) UpdateDatacenter(ctx context.Context, dc *Datacenter, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, dc, domain) +func (p *Mock) UpdateDatacenter(ctx context.Context, req UpdateDatacenterRequest) (*UpdateDatacenterResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdateDatacenterResponse), args.Error(1) } -func (p *Mock) ListDatacenters(ctx context.Context, domain string) ([]*Datacenter, error) { - args := p.Called(ctx, domain) +func (p *Mock) ListDatacenters(ctx context.Context, req ListDatacentersRequest) ([]Datacenter, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*Datacenter), args.Error(1) + return args.Get(0).([]Datacenter), args.Error(1) } func (p *Mock) CreateIPv4DefaultDatacenter(ctx context.Context, domain string) (*Datacenter, error) { @@ -214,203 +214,202 @@ func (p *Mock) CreateMapsDefaultDatacenter(ctx context.Context, domainName strin return args.Get(0).(*Datacenter), args.Error(1) } -func (p *Mock) GetResource(ctx context.Context, resource string, domain string) (*Resource, error) { - args := p.Called(ctx, resource, domain) +func (p *Mock) GetResource(ctx context.Context, req GetResourceRequest) (*GetResourceResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*Resource), args.Error(1) + return args.Get(0).(*GetResourceResponse), args.Error(1) } -func (p *Mock) CreateResource(ctx context.Context, resource *Resource, domain string) (*ResourceResponse, error) { - args := p.Called(ctx, resource, domain) +func (p *Mock) CreateResource(ctx context.Context, req CreateResourceRequest) (*CreateResourceResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResourceResponse), args.Error(1) + return args.Get(0).(*CreateResourceResponse), args.Error(1) } -func (p *Mock) DeleteResource(ctx context.Context, resource *Resource, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, resource, domain) +func (p *Mock) DeleteResource(ctx context.Context, req DeleteResourceRequest) (*DeleteResourceResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeleteResourceResponse), args.Error(1) } -func (p *Mock) UpdateResource(ctx context.Context, resource *Resource, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, resource, domain) +func (p *Mock) UpdateResource(ctx context.Context, req UpdateResourceRequest) (*UpdateResourceResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdateResourceResponse), args.Error(1) } -func (p *Mock) ListResources(ctx context.Context, domain string) ([]*Resource, error) { - args := p.Called(ctx, domain) +func (p *Mock) ListResources(ctx context.Context, req ListResourcesRequest) ([]Resource, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*Resource), args.Error(1) + return args.Get(0).([]Resource), args.Error(1) } -func (p *Mock) GetASMap(ctx context.Context, asMap string, domain string) (*ASMap, error) { - args := p.Called(ctx, asMap, domain) +func (p *Mock) GetASMap(ctx context.Context, req GetASMapRequest) (*GetASMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ASMap), args.Error(1) + return args.Get(0).(*GetASMapResponse), args.Error(1) } -func (p *Mock) CreateASMap(ctx context.Context, asMap *ASMap, domain string) (*ASMapResponse, error) { - args := p.Called(ctx, asMap, domain) +func (p *Mock) CreateASMap(ctx context.Context, req CreateASMapRequest) (*CreateASMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ASMapResponse), args.Error(1) + return args.Get(0).(*CreateASMapResponse), args.Error(1) } -func (p *Mock) DeleteASMap(ctx context.Context, asMap *ASMap, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, asMap, domain) +func (p *Mock) DeleteASMap(ctx context.Context, req DeleteASMapRequest) (*DeleteASMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeleteASMapResponse), args.Error(1) } -func (p *Mock) UpdateASMap(ctx context.Context, asMap *ASMap, domain string) (*ResponseStatus, error) { - - args := p.Called(ctx, asMap, domain) +func (p *Mock) UpdateASMap(ctx context.Context, req UpdateASMapRequest) (*UpdateASMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdateASMapResponse), args.Error(1) } -func (p *Mock) ListASMaps(ctx context.Context, domain string) ([]*ASMap, error) { - args := p.Called(ctx, domain) +func (p *Mock) ListASMaps(ctx context.Context, req ListASMapsRequest) ([]ASMap, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*ASMap), args.Error(1) + return args.Get(0).([]ASMap), args.Error(1) } -func (p *Mock) GetGeoMap(ctx context.Context, geo string, domain string) (*GeoMap, error) { - args := p.Called(ctx, geo, domain) +func (p *Mock) GetGeoMap(ctx context.Context, req GetGeoMapRequest) (*GetGeoMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*GeoMap), args.Error(1) + return args.Get(0).(*GetGeoMapResponse), args.Error(1) } -func (p *Mock) CreateGeoMap(ctx context.Context, geo *GeoMap, domain string) (*GeoMapResponse, error) { - args := p.Called(ctx, geo, domain) +func (p *Mock) CreateGeoMap(ctx context.Context, req CreateGeoMapRequest) (*CreateGeoMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*GeoMapResponse), args.Error(1) + return args.Get(0).(*CreateGeoMapResponse), args.Error(1) } -func (p *Mock) DeleteGeoMap(ctx context.Context, geo *GeoMap, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, geo, domain) +func (p *Mock) DeleteGeoMap(ctx context.Context, req DeleteGeoMapRequest) (*DeleteGeoMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeleteGeoMapResponse), args.Error(1) } -func (p *Mock) UpdateGeoMap(ctx context.Context, geo *GeoMap, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, geo, domain) +func (p *Mock) UpdateGeoMap(ctx context.Context, req UpdateGeoMapRequest) (*UpdateGeoMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdateGeoMapResponse), args.Error(1) } -func (p *Mock) ListGeoMaps(ctx context.Context, domain string) ([]*GeoMap, error) { - args := p.Called(ctx, domain) +func (p *Mock) ListGeoMaps(ctx context.Context, req ListGeoMapsRequest) ([]GeoMap, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*GeoMap), args.Error(1) + return args.Get(0).([]GeoMap), args.Error(1) } -func (p *Mock) GetCIDRMap(ctx context.Context, cidr string, domain string) (*CIDRMap, error) { - args := p.Called(ctx, cidr, domain) +func (p *Mock) GetCIDRMap(ctx context.Context, req GetCIDRMapRequest) (*GetCIDRMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*CIDRMap), args.Error(1) + return args.Get(0).(*GetCIDRMapResponse), args.Error(1) } -func (p *Mock) CreateCIDRMap(ctx context.Context, cidr *CIDRMap, domain string) (*CIDRMapResponse, error) { - args := p.Called(ctx, cidr, domain) +func (p *Mock) CreateCIDRMap(ctx context.Context, req CreateCIDRMapRequest) (*CreateCIDRMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*CIDRMapResponse), args.Error(1) + return args.Get(0).(*CreateCIDRMapResponse), args.Error(1) } -func (p *Mock) DeleteCIDRMap(ctx context.Context, cidr *CIDRMap, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, cidr, domain) +func (p *Mock) DeleteCIDRMap(ctx context.Context, req DeleteCIDRMapRequest) (*DeleteCIDRMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*DeleteCIDRMapResponse), args.Error(1) } -func (p *Mock) UpdateCIDRMap(ctx context.Context, cidr *CIDRMap, domain string) (*ResponseStatus, error) { - args := p.Called(ctx, cidr, domain) +func (p *Mock) UpdateCIDRMap(ctx context.Context, req UpdateCIDRMapRequest) (*UpdateCIDRMapResponse, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ResponseStatus), args.Error(1) + return args.Get(0).(*UpdateCIDRMapResponse), args.Error(1) } -func (p *Mock) ListCIDRMaps(ctx context.Context, domain string) ([]*CIDRMap, error) { - args := p.Called(ctx, domain) +func (p *Mock) ListCIDRMaps(ctx context.Context, req ListCIDRMapsRequest) ([]CIDRMap, error) { + args := p.Called(ctx, req) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*CIDRMap), args.Error(1) + return args.Get(0).([]CIDRMap), args.Error(1) } diff --git a/pkg/gtm/property.go b/pkg/gtm/property.go index 86bb5322..abff6c24 100644 --- a/pkg/gtm/property.go +++ b/pkg/gtm/property.go @@ -2,135 +2,215 @@ package gtm import ( "context" + "errors" "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) -// Properties contains operations available on a Property resource. -type Properties interface { - // ListProperties retrieves all Properties for the provided domainName. - // - // See: https://techdocs.akamai.com/gtm/reference/get-properties - ListProperties(context.Context, string) ([]*Property, error) - // GetProperty retrieves a Property with the given domain and property names. - // - // See: https://techdocs.akamai.com/gtm/reference/get-property - GetProperty(context.Context, string, string) (*Property, error) - // CreateProperty creates property. - // - // See: https://techdocs.akamai.com/gtm/reference/put-property - CreateProperty(context.Context, *Property, string) (*PropertyResponse, error) - // DeleteProperty is a method applied to a property object resulting in removal. - // - // See: https://techdocs.akamai.com/gtm/reference/delete-property - DeleteProperty(context.Context, *Property, string) (*ResponseStatus, error) - // UpdateProperty is a method applied to a property object resulting in an update. - // - // See: https://techdocs.akamai.com/gtm/reference/put-property - UpdateProperty(context.Context, *Property, string) (*ResponseStatus, error) -} +type ( + // TrafficTarget struct contains information about where to direct data center traffic + TrafficTarget struct { + DatacenterID int `json:"datacenterId"` + Enabled bool `json:"enabled"` + Weight float64 `json:"weight,omitempty"` + Servers []string `json:"servers,omitempty"` + Name string `json:"name,omitempty"` + HandoutCName string `json:"handoutCName,omitempty"` + Precedence *int `json:"precedence,omitempty"` + } -// TrafficTarget struct contains information about where to direct data center traffic -type TrafficTarget struct { - DatacenterID int `json:"datacenterId"` - Enabled bool `json:"enabled"` - Weight float64 `json:"weight,omitempty"` - Servers []string `json:"servers,omitempty"` - Name string `json:"name,omitempty"` - HandoutCName string `json:"handoutCName,omitempty"` - Precedence *int `json:"precedence,omitempty"` -} + // HTTPHeader struct contains HTTP headers to send if the testObjectProtocol is http or https + HTTPHeader struct { + Name string `json:"name"` + Value string `json:"value"` + } + + // LivenessTest contains configuration of liveness tests to determine whether your servers respond to requests + LivenessTest struct { + Name string `json:"name"` + ErrorPenalty float64 `json:"errorPenalty,omitempty"` + PeerCertificateVerification bool `json:"peerCertificateVerification"` + TestInterval int `json:"testInterval,omitempty"` + TestObject string `json:"testObject,omitempty"` + Links []Link `json:"links,omitempty"` + RequestString string `json:"requestString,omitempty"` + ResponseString string `json:"responseString,omitempty"` + HTTPError3xx bool `json:"httpError3xx"` + HTTPError4xx bool `json:"httpError4xx"` + HTTPError5xx bool `json:"httpError5xx"` + HTTPMethod *string `json:"httpMethod"` + HTTPRequestBody *string `json:"httpRequestBody"` + Disabled bool `json:"disabled"` + TestObjectProtocol string `json:"testObjectProtocol,omitempty"` + TestObjectPassword string `json:"testObjectPassword,omitempty"` + TestObjectPort int `json:"testObjectPort,omitempty"` + SSLClientPrivateKey string `json:"sslClientPrivateKey,omitempty"` + SSLClientCertificate string `json:"sslClientCertificate,omitempty"` + Pre2023SecurityPosture bool `json:"pre2023SecurityPosture"` + DisableNonstandardPortWarning bool `json:"disableNonstandardPortWarning"` + HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"` + TestObjectUsername string `json:"testObjectUsername,omitempty"` + TestTimeout float32 `json:"testTimeout,omitempty"` + TimeoutPenalty float64 `json:"timeoutPenalty,omitempty"` + AnswersRequired bool `json:"answersRequired"` + ResourceType string `json:"resourceType,omitempty"` + RecursionRequested bool `json:"recursionRequested"` + AlternateCACertificates []string `json:"alternateCACertificates"` + } + + // StaticRRSet contains static recordset + StaticRRSet struct { + Type string `json:"type"` + TTL int `json:"ttl"` + Rdata []string `json:"rdata"` + } + + // Property represents a GTM property + Property struct { + Name string `json:"name"` + Type string `json:"type"` + IPv6 bool `json:"ipv6"` + ScoreAggregationType string `json:"scoreAggregationType"` + StickinessBonusPercentage int `json:"stickinessBonusPercentage,omitempty"` + StickinessBonusConstant int `json:"stickinessBonusConstant,omitempty"` + HealthThreshold float64 `json:"healthThreshold,omitempty"` + UseComputedTargets bool `json:"useComputedTargets"` + BackupIP string `json:"backupIp,omitempty"` + BalanceByDownloadScore bool `json:"balanceByDownloadScore"` + StaticTTL int `json:"staticTTL,omitempty"` + StaticRRSets []StaticRRSet `json:"staticRRSets,omitempty"` + LastModified string `json:"lastModified"` + UnreachableThreshold float64 `json:"unreachableThreshold,omitempty"` + MinLiveFraction float64 `json:"minLiveFraction,omitempty"` + HealthMultiplier float64 `json:"healthMultiplier,omitempty"` + DynamicTTL int `json:"dynamicTTL,omitempty"` + MaxUnreachablePenalty int `json:"maxUnreachablePenalty,omitempty"` + MapName string `json:"mapName,omitempty"` + HandoutLimit int `json:"handoutLimit"` + HandoutMode string `json:"handoutMode"` + FailoverDelay int `json:"failoverDelay,omitempty"` + BackupCName string `json:"backupCName,omitempty"` + FailbackDelay int `json:"failbackDelay,omitempty"` + LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` + HealthMax float64 `json:"healthMax,omitempty"` + GhostDemandReporting bool `json:"ghostDemandReporting"` + Comments string `json:"comments,omitempty"` + CName string `json:"cname,omitempty"` + WeightedHashBitsForIPv4 int `json:"weightedHashBitsForIPv4,omitempty"` + WeightedHashBitsForIPv6 int `json:"weightedHashBitsForIPv6,omitempty"` + TrafficTargets []TrafficTarget `json:"trafficTargets,omitempty"` + Links []Link `json:"links,omitempty"` + LivenessTests []LivenessTest `json:"livenessTests,omitempty"` + } + + // PropertyRequest contains request parameters + PropertyRequest struct { + Property *Property + DomainName string + } + + // PropertyList contains a list of property items + PropertyList struct { + PropertyItems []Property `json:"items"` + } + // GetPropertyRequest contains request parameters for GetProperty + GetPropertyRequest struct { + DomainName string + PropertyName string + } + + // GetPropertyResponse contains the response data from GetProperty operation + GetPropertyResponse Property -// HTTPHeader struct contains HTTP headers to send if the testObjectProtocol is http or https -type HTTPHeader struct { - Name string `json:"name"` - Value string `json:"value"` + // ListPropertiesRequest contains request parameters for ListProperties + ListPropertiesRequest struct { + DomainName string + } + + // CreatePropertyRequest contains request parameters for CreateProperty + CreatePropertyRequest PropertyRequest + + // CreatePropertyResponse contains the response data from CreateProperty operation + CreatePropertyResponse struct { + Resource *Property `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // UpdatePropertyRequest contains request parameters for UpdatePropertyResponse + UpdatePropertyRequest PropertyRequest + + // UpdatePropertyResponse contains the response data from UpdatePropertyResponse operation + UpdatePropertyResponse struct { + Resource *Property `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // DeletePropertyRequest contains request parameters for DeleteProperty + DeletePropertyRequest struct { + DomainName string + PropertyName string + } + + // DeletePropertyResponse contains the response data from DeleteProperty operation + DeletePropertyResponse struct { + Resource *Property `json:"resource"` + Status *ResponseStatus `json:"status"` + } +) + +var ( + // ErrGetProperty is returned when GetProperty fails. + ErrGetProperty = errors.New("get property") + // ErrListProperties is returned when ListProperties fails. + ErrListProperties = errors.New("list properties") + // ErrCreateProperty is returned when CreateProperty fails. + ErrCreateProperty = errors.New("create Property") + // ErrUpdateProperty is returned when UpdateProperty fails + ErrUpdateProperty = errors.New("update Property") + // ErrDeleteProperty is returned when DeleteProperty fails + ErrDeleteProperty = errors.New("delete Property") +) + +// Validate validates GetPropertyRequest +func (r GetPropertyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "PropertyName": validation.Validate(r.PropertyName, validation.Required), + }) } -// LivenessTest contains configuration of liveness tests to determine whether your servers respond to requests -type LivenessTest struct { - Name string `json:"name"` - ErrorPenalty float64 `json:"errorPenalty,omitempty"` - PeerCertificateVerification bool `json:"peerCertificateVerification"` - TestInterval int `json:"testInterval,omitempty"` - TestObject string `json:"testObject,omitempty"` - Links []*Link `json:"links,omitempty"` - RequestString string `json:"requestString,omitempty"` - ResponseString string `json:"responseString,omitempty"` - HTTPError3xx bool `json:"httpError3xx"` - HTTPError4xx bool `json:"httpError4xx"` - HTTPError5xx bool `json:"httpError5xx"` - HTTPMethod *string `json:"httpMethod"` - HTTPRequestBody *string `json:"httpRequestBody"` - Disabled bool `json:"disabled"` - TestObjectProtocol string `json:"testObjectProtocol,omitempty"` - TestObjectPassword string `json:"testObjectPassword,omitempty"` - TestObjectPort int `json:"testObjectPort,omitempty"` - SSLClientPrivateKey string `json:"sslClientPrivateKey,omitempty"` - SSLClientCertificate string `json:"sslClientCertificate,omitempty"` - Pre2023SecurityPosture bool `json:"pre2023SecurityPosture"` - DisableNonstandardPortWarning bool `json:"disableNonstandardPortWarning"` - HTTPHeaders []*HTTPHeader `json:"httpHeaders,omitempty"` - TestObjectUsername string `json:"testObjectUsername,omitempty"` - TestTimeout float32 `json:"testTimeout,omitempty"` - TimeoutPenalty float64 `json:"timeoutPenalty,omitempty"` - AnswersRequired bool `json:"answersRequired"` - ResourceType string `json:"resourceType,omitempty"` - RecursionRequested bool `json:"recursionRequested"` - AlternateCACertificates []string `json:"alternateCACertificates"` +// Validate validates ListPropertiesRequest +func (r ListPropertiesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) } -// StaticRRSet contains static recordset -type StaticRRSet struct { - Type string `json:"type"` - TTL int `json:"ttl"` - Rdata []string `json:"rdata"` +// Validate validates CreatePropertyRequest +func (r CreatePropertyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "Property": validation.Validate(r.Property, validation.Required), + }) } -// Property represents a GTM property -type Property struct { - Name string `json:"name"` - Type string `json:"type"` - IPv6 bool `json:"ipv6"` - ScoreAggregationType string `json:"scoreAggregationType"` - StickinessBonusPercentage int `json:"stickinessBonusPercentage,omitempty"` - StickinessBonusConstant int `json:"stickinessBonusConstant,omitempty"` - HealthThreshold float64 `json:"healthThreshold,omitempty"` - UseComputedTargets bool `json:"useComputedTargets"` - BackupIP string `json:"backupIp,omitempty"` - BalanceByDownloadScore bool `json:"balanceByDownloadScore"` - StaticTTL int `json:"staticTTL,omitempty"` - StaticRRSets []*StaticRRSet `json:"staticRRSets,omitempty"` - LastModified string `json:"lastModified"` - UnreachableThreshold float64 `json:"unreachableThreshold,omitempty"` - MinLiveFraction float64 `json:"minLiveFraction,omitempty"` - HealthMultiplier float64 `json:"healthMultiplier,omitempty"` - DynamicTTL int `json:"dynamicTTL,omitempty"` - MaxUnreachablePenalty int `json:"maxUnreachablePenalty,omitempty"` - MapName string `json:"mapName,omitempty"` - HandoutLimit int `json:"handoutLimit"` - HandoutMode string `json:"handoutMode"` - FailoverDelay int `json:"failoverDelay,omitempty"` - BackupCName string `json:"backupCName,omitempty"` - FailbackDelay int `json:"failbackDelay,omitempty"` - LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` - HealthMax float64 `json:"healthMax,omitempty"` - GhostDemandReporting bool `json:"ghostDemandReporting"` - Comments string `json:"comments,omitempty"` - CName string `json:"cname,omitempty"` - WeightedHashBitsForIPv4 int `json:"weightedHashBitsForIPv4,omitempty"` - WeightedHashBitsForIPv6 int `json:"weightedHashBitsForIPv6,omitempty"` - TrafficTargets []*TrafficTarget `json:"trafficTargets,omitempty"` - Links []*Link `json:"links,omitempty"` - LivenessTests []*LivenessTest `json:"livenessTests,omitempty"` +// Validate validates UpdatePropertyRequest +func (r UpdatePropertyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "Property": validation.Validate(r.Property, validation.Required), + }) } -// PropertyList contains a list of property items -type PropertyList struct { - PropertyItems []*Property `json:"items"` +// Validate validates DeletePropertyRequest +func (r DeletePropertyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "PropertyName": validation.Validate(r.PropertyName, validation.Required), + }) } // Validate validates Property @@ -146,7 +226,7 @@ func (p *Property) Validate() error { // validateRankedFailoverTrafficTargets validates traffic targets when property type is 'ranked-failover' func validateRankedFailoverTrafficTargets(value interface{}) error { - tt := value.([]*TrafficTarget) + tt := value.([]TrafficTarget) if len(tt) == 0 { return fmt.Errorf("no traffic targets are enabled") } @@ -173,11 +253,15 @@ func validateRankedFailoverTrafficTargets(value interface{}) error { return nil } -func (g *gtm) ListProperties(ctx context.Context, domainName string) ([]*Property, error) { +func (g *gtm) ListProperties(ctx context.Context, params ListPropertiesRequest) ([]Property, error) { logger := g.Log(ctx) logger.Debug("ListProperties") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrListProperties, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListProperties request: %w", err) @@ -197,18 +281,22 @@ func (g *gtm) ListProperties(ctx context.Context, domainName string) ([]*Propert return result.PropertyItems, nil } -func (g *gtm) GetProperty(ctx context.Context, propertyName, domainName string) (*Property, error) { +func (g *gtm) GetProperty(ctx context.Context, params GetPropertyRequest) (*GetPropertyResponse, error) { logger := g.Log(ctx) logger.Debug("GetProperty") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", domainName, propertyName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetProperty, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", params.DomainName, params.PropertyName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetProperty request: %w", err) } setVersionHeader(req, schemaVersion) - var result Property + var result GetPropertyResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetProperty request failed: %w", err) @@ -221,67 +309,78 @@ func (g *gtm) GetProperty(ctx context.Context, propertyName, domainName string) return &result, nil } -func (g *gtm) CreateProperty(ctx context.Context, property *Property, domainName string) (*PropertyResponse, error) { +func (g *gtm) CreateProperty(ctx context.Context, params CreatePropertyRequest) (*CreatePropertyResponse, error) { logger := g.Log(ctx) logger.Debug("CreateProperty") - return property.save(ctx, g, domainName) -} + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateProperty, ErrStructValidation, err) + } -func (g *gtm) UpdateProperty(ctx context.Context, property *Property, domainName string) (*ResponseStatus, error) { - logger := g.Log(ctx) - logger.Debug("UpdateProperty") + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", params.DomainName, params.Property.Name) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create Property request: %w", err) + } + setVersionHeader(req, schemaVersion) - stat, err := property.save(ctx, g, domainName) + var result CreatePropertyResponse + resp, err := g.Exec(req, &result, params.Property) if err != nil { - return nil, err + return nil, fmt.Errorf("property request failed: %w", err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, g.Error(resp) } - return stat.Status, err + + return &result, nil } -// Save Property updates method -func (p *Property) save(ctx context.Context, g *gtm, domainName string) (*PropertyResponse, error) { +func (g *gtm) UpdateProperty(ctx context.Context, params UpdatePropertyRequest) (*UpdatePropertyResponse, error) { + logger := g.Log(ctx) + logger.Debug("UpdateProperty") - if err := p.Validate(); err != nil { - return nil, fmt.Errorf("property validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateProperty, ErrStructValidation, err) } - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", domainName, p.Name) + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", params.DomainName, params.Property.Name) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Property request: %w", err) } setVersionHeader(req, schemaVersion) - var result PropertyResponse - resp, err := g.Exec(req, &result, p) + var result UpdatePropertyResponse + resp, err := g.Exec(req, &result, params.Property) if err != nil { return nil, fmt.Errorf("property request failed: %w", err) } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusOK { return nil, g.Error(resp) } return &result, nil } -func (g *gtm) DeleteProperty(ctx context.Context, property *Property, domainName string) (*ResponseStatus, error) { +func (g *gtm) DeleteProperty(ctx context.Context, params DeletePropertyRequest) (*DeletePropertyResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteProperty") - if err := property.Validate(); err != nil { - return nil, fmt.Errorf("DeleteProperty validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteProperty, ErrStructValidation, err) } - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", domainName, property.Name) + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/properties/%s", params.DomainName, params.PropertyName) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Property request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseBody + var result DeletePropertyResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("DeleteProperty request failed: %w", err) @@ -291,5 +390,5 @@ func (g *gtm) DeleteProperty(ctx context.Context, property *Property, domainName return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } diff --git a/pkg/gtm/property_test.go b/pkg/gtm/property_test.go index 90e7beeb..fd5ba543 100644 --- a/pkg/gtm/property_test.go +++ b/pkg/gtm/property_test.go @@ -9,8 +9,8 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,16 +28,18 @@ func TestGTM_ListProperties(t *testing.T) { } tests := map[string]struct { - domain string + params ListPropertiesRequest responseStatus int responseBody string expectedPath string - expectedResponse []*Property + expectedResponse []Property withError error headers http.Header }{ "200 OK": { - domain: "example.akadns.net", + params: ListPropertiesRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -47,7 +49,9 @@ func TestGTM_ListProperties(t *testing.T) { expectedResponse: result.PropertyItems, }, "500 internal server error": { - domain: "example.akadns.net", + params: ListPropertiesRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -80,7 +84,7 @@ func TestGTM_ListProperties(t *testing.T) { result, err := client.ListProperties( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -92,7 +96,7 @@ func TestGTM_ListProperties(t *testing.T) { } func TestGTM_GetProperty(t *testing.T) { - var result Property + var result GetPropertyResponse respData, err := loadTestData("TestGTM_GetProperty.resp.json") if err != nil { @@ -104,25 +108,28 @@ func TestGTM_GetProperty(t *testing.T) { } tests := map[string]struct { - name string - domain string + params GetPropertyRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *Property + expectedResponse *GetPropertyResponse withError error }{ "200 OK": { - name: "www", - domain: "example.akadns.net", + params: GetPropertyRequest{ + PropertyName: "www", + DomainName: "example.akadns.net", + }, responseStatus: http.StatusOK, responseBody: respData, expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/www", expectedResponse: &result, }, "500 internal server error": { - name: "www", - domain: "example.akadns.net", + params: GetPropertyRequest{ + PropertyName: "www", + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -150,7 +157,7 @@ func TestGTM_GetProperty(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetProperty(context.Background(), test.name, test.domain) + result, err := client.GetProperty(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -163,57 +170,58 @@ func TestGTM_GetProperty(t *testing.T) { func TestGTM_CreateProperty(t *testing.T) { tests := map[string]struct { - domain string - property *Property + params CreatePropertyRequest responseStatus int responseBody string expectedPath string - expectedResponse *PropertyResponse + expectedResponse *CreatePropertyResponse withError bool assertError func(*testing.T, error) headers http.Header }{ "201 Created": { - property: &Property{ - BalanceByDownloadScore: false, - HandoutMode: "normal", - IPv6: false, - Name: "origin", - ScoreAggregationType: "mean", - StaticTTL: 600, - Type: "weighted-round-robin", - UseComputedTargets: false, - LivenessTests: []*LivenessTest{ - { - DisableNonstandardPortWarning: false, - HTTPError3xx: true, - HTTPError4xx: true, - HTTPError5xx: true, - Name: "health-check", - TestInterval: 60, - TestObject: "/status", - TestObjectPort: 80, - TestObjectProtocol: "HTTP", - TestTimeout: 25.0, - }, - }, - TrafficTargets: []*TrafficTarget{ - { - DatacenterID: 3134, - Enabled: true, - Weight: 50.0, - Servers: []string{"1.2.3.5"}, + params: CreatePropertyRequest{ + Property: &Property{ + BalanceByDownloadScore: false, + HandoutMode: "normal", + IPv6: false, + Name: "origin", + ScoreAggregationType: "mean", + StaticTTL: 600, + Type: "weighted-round-robin", + UseComputedTargets: false, + LivenessTests: []LivenessTest{ + { + DisableNonstandardPortWarning: false, + HTTPError3xx: true, + HTTPError4xx: true, + HTTPError5xx: true, + Name: "health-check", + TestInterval: 60, + TestObject: "/status", + TestObjectPort: 80, + TestObjectProtocol: "HTTP", + TestTimeout: 25.0, + }, }, - { - DatacenterID: 3133, - Enabled: true, - Weight: 50.0, - Servers: []string{"1.2.3.4"}, - Precedence: nil, + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 3134, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.5"}, + }, + { + DatacenterID: 3133, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.4"}, + Precedence: nil, + }, }, }, + DomainName: "example.akadns.net", }, - domain: "example.akadns.net", headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -314,7 +322,7 @@ func TestGTM_CreateProperty(t *testing.T) { } } `, - expectedResponse: &PropertyResponse{ + expectedResponse: &CreatePropertyResponse{ Resource: &Property{ BalanceByDownloadScore: false, HandoutMode: "normal", @@ -325,7 +333,7 @@ func TestGTM_CreateProperty(t *testing.T) { DynamicTTL: 300, Type: "weighted-round-robin", UseComputedTargets: false, - LivenessTests: []*LivenessTest{ + LivenessTests: []LivenessTest{ { DisableNonstandardPortWarning: false, HTTPError3xx: true, @@ -339,7 +347,7 @@ func TestGTM_CreateProperty(t *testing.T) { TestTimeout: 25.0, }, }, - TrafficTargets: []*TrafficTarget{ + TrafficTargets: []TrafficTarget{ { DatacenterID: 3134, Enabled: true, @@ -353,7 +361,7 @@ func TestGTM_CreateProperty(t *testing.T) { Servers: []string{"1.2.3.4"}, }, }, - Links: []*Link{ + Links: []Link{ { Href: "/config-gtm/v1/domains/example.akadns.net/properties/origin", Rel: "self", @@ -366,7 +374,7 @@ func TestGTM_CreateProperty(t *testing.T) { PassingValidation: true, PropagationStatus: "PENDING", PropagationStatusDate: "2014-04-15T11:30:27.000+0000", - Links: &[]Link{ + Links: []Link{ { Href: "/config-gtm/v1/domains/example.akadns.net/status/current", Rel: "self", @@ -374,54 +382,56 @@ func TestGTM_CreateProperty(t *testing.T) { }, }, }, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/origin", }, "201 Created - ranked-failover": { - property: &Property{ - BalanceByDownloadScore: false, - HandoutMode: "normal", - IPv6: false, - Name: "origin", - ScoreAggregationType: "mean", - StaticTTL: 600, - Type: "ranked-failover", - UseComputedTargets: false, - LivenessTests: []*LivenessTest{ - { - DisableNonstandardPortWarning: false, - HTTPError3xx: true, - HTTPError4xx: true, - HTTPError5xx: true, - HTTPMethod: tools.StringPtr("GET"), - HTTPRequestBody: tools.StringPtr("TestBody"), - Name: "health-check", - TestInterval: 60, - TestObject: "/status", - TestObjectPort: 80, - TestObjectProtocol: "HTTP", - TestTimeout: 25.0, - Pre2023SecurityPosture: true, - AlternateCACertificates: []string{"test1"}, - }, - }, - TrafficTargets: []*TrafficTarget{ - { - DatacenterID: 3134, - Enabled: true, - Weight: 50.0, - Servers: []string{"1.2.3.5"}, - Precedence: tools.IntPtr(255), + params: CreatePropertyRequest{ + Property: &Property{ + BalanceByDownloadScore: false, + HandoutMode: "normal", + IPv6: false, + Name: "origin", + ScoreAggregationType: "mean", + StaticTTL: 600, + Type: "ranked-failover", + UseComputedTargets: false, + LivenessTests: []LivenessTest{ + { + DisableNonstandardPortWarning: false, + HTTPError3xx: true, + HTTPError4xx: true, + HTTPError5xx: true, + HTTPMethod: ptr.To("GET"), + HTTPRequestBody: ptr.To("TestBody"), + Name: "health-check", + TestInterval: 60, + TestObject: "/status", + TestObjectPort: 80, + TestObjectProtocol: "HTTP", + TestTimeout: 25.0, + Pre2023SecurityPosture: true, + AlternateCACertificates: []string{"test1"}, + }, }, - { - DatacenterID: 3133, - Enabled: true, - Weight: 50.0, - Servers: []string{"1.2.3.4"}, - Precedence: nil, + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 3134, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.5"}, + Precedence: ptr.To(255), + }, + { + DatacenterID: 3133, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.4"}, + Precedence: nil, + }, }, }, + DomainName: "example.akadns.net", }, - domain: "example.akadns.net", headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -526,7 +536,7 @@ func TestGTM_CreateProperty(t *testing.T) { } } `, - expectedResponse: &PropertyResponse{ + expectedResponse: &CreatePropertyResponse{ Resource: &Property{ BalanceByDownloadScore: false, HandoutMode: "normal", @@ -537,14 +547,14 @@ func TestGTM_CreateProperty(t *testing.T) { DynamicTTL: 300, Type: "weighted-round-robin", UseComputedTargets: false, - LivenessTests: []*LivenessTest{ + LivenessTests: []LivenessTest{ { DisableNonstandardPortWarning: false, HTTPError3xx: true, HTTPError4xx: true, HTTPError5xx: true, - HTTPMethod: tools.StringPtr("GET"), - HTTPRequestBody: tools.StringPtr("TestBody"), + HTTPMethod: ptr.To("GET"), + HTTPRequestBody: ptr.To("TestBody"), Pre2023SecurityPosture: true, AlternateCACertificates: []string{"test1"}, Name: "health-check", @@ -555,13 +565,13 @@ func TestGTM_CreateProperty(t *testing.T) { TestTimeout: 25.0, }, }, - TrafficTargets: []*TrafficTarget{ + TrafficTargets: []TrafficTarget{ { DatacenterID: 3134, Enabled: true, Weight: 50.0, Servers: []string{"1.2.3.5"}, - Precedence: tools.IntPtr(255), + Precedence: ptr.To(255), }, { DatacenterID: 3133, @@ -571,7 +581,7 @@ func TestGTM_CreateProperty(t *testing.T) { Precedence: nil, }, }, - Links: []*Link{ + Links: []Link{ { Href: "/config-gtm/v1/domains/example.akadns.net/properties/origin", Rel: "self", @@ -584,7 +594,7 @@ func TestGTM_CreateProperty(t *testing.T) { PassingValidation: true, PropagationStatus: "PENDING", PropagationStatusDate: "2014-04-15T11:30:27.000+0000", - Links: &[]Link{ + Links: []Link{ { Href: "/config-gtm/v1/domains/example.akadns.net/status/current", Rel: "self", @@ -592,24 +602,26 @@ func TestGTM_CreateProperty(t *testing.T) { }, }, }, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/origin", }, "validation error - missing precedence for ranked-failover property type": { - property: &Property{ - Type: "ranked-failover", - Name: "property", - HandoutMode: "normal", - ScoreAggregationType: "mean", - TrafficTargets: []*TrafficTarget{ - { - DatacenterID: 1, - Enabled: false, - Precedence: nil, - }, - { - DatacenterID: 2, - Enabled: false, - Precedence: nil, + params: CreatePropertyRequest{ + Property: &Property{ + Type: "ranked-failover", + Name: "property", + HandoutMode: "normal", + ScoreAggregationType: "mean", + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 1, + Enabled: false, + Precedence: nil, + }, + { + DatacenterID: 2, + Enabled: false, + Precedence: nil, + }, }, }, }, @@ -619,32 +631,36 @@ func TestGTM_CreateProperty(t *testing.T) { }, }, "validation error - precedence value over the limit": { - property: &Property{ - Type: "ranked-failover", - Name: "property", - HandoutMode: "normal", - ScoreAggregationType: "mean", - TrafficTargets: []*TrafficTarget{ - { - DatacenterID: 1, - Enabled: false, - Precedence: tools.IntPtr(256), + params: CreatePropertyRequest{ + Property: &Property{ + Type: "ranked-failover", + Name: "property", + HandoutMode: "normal", + ScoreAggregationType: "mean", + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 1, + Enabled: false, + Precedence: ptr.To(256), + }, }, }, }, withError: true, assertError: func(t *testing.T, err error) { - assert.ErrorContains(t, err, "property validation failed. TrafficTargets: 'Precedence' value has to be between 0 and 255") + assert.ErrorContains(t, err, "TrafficTargets: 'Precedence' value has to be between 0 and 255") }, }, "500 internal server error": { - property: &Property{ - Name: "testName", - HandoutMode: "normal", - ScoreAggregationType: "mean", - Type: "failover", + params: CreatePropertyRequest{ + Property: &Property{ + Name: "testName", + HandoutMode: "normal", + ScoreAggregationType: "mean", + Type: "failover", + }, + DomainName: "example.akadns.net", }, - domain: "example.akadns.net", responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -652,7 +668,7 @@ func TestGTM_CreateProperty(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating domain" }`, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/testName", withError: true, assertError: func(t *testing.T, err error) { want := &Error{ @@ -669,6 +685,7 @@ func TestGTM_CreateProperty(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -680,7 +697,7 @@ func TestGTM_CreateProperty(t *testing.T) { result, err := client.CreateProperty( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.property, test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError { test.assertError(t, err) return @@ -693,62 +710,63 @@ func TestGTM_CreateProperty(t *testing.T) { func TestGTM_UpdateProperty(t *testing.T) { tests := map[string]struct { - property *Property - domain string + params UpdatePropertyRequest responseStatus int responseBody string expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdatePropertyResponse withError bool assertError func(*testing.T, error) headers http.Header }{ "200 Success": { - property: &Property{ - BalanceByDownloadScore: false, - HandoutMode: "normal", - IPv6: false, - Name: "origin", - ScoreAggregationType: "mean", - StaticTTL: 600, - Type: "weighted-round-robin", - UseComputedTargets: false, - LivenessTests: []*LivenessTest{ - { - DisableNonstandardPortWarning: false, - HTTPError3xx: true, - HTTPError4xx: true, - HTTPError5xx: true, - Name: "health-check", - TestInterval: 60, - TestObject: "/status", - TestObjectPort: 80, - TestObjectProtocol: "HTTP", - TestTimeout: 25.0, - }, - }, - TrafficTargets: []*TrafficTarget{ - { - DatacenterID: 3134, - Enabled: true, - Weight: 50.0, - Servers: []string{"1.2.3.5"}, - Precedence: tools.IntPtr(255), + params: UpdatePropertyRequest{ + Property: &Property{ + BalanceByDownloadScore: false, + HandoutMode: "normal", + IPv6: false, + Name: "origin", + ScoreAggregationType: "mean", + StaticTTL: 600, + Type: "weighted-round-robin", + UseComputedTargets: false, + LivenessTests: []LivenessTest{ + { + DisableNonstandardPortWarning: false, + HTTPError3xx: true, + HTTPError4xx: true, + HTTPError5xx: true, + Name: "health-check", + TestInterval: 60, + TestObject: "/status", + TestObjectPort: 80, + TestObjectProtocol: "HTTP", + TestTimeout: 25.0, + }, }, - { - DatacenterID: 3133, - Enabled: true, - Weight: 50.0, - Servers: []string{"1.2.3.4"}, - Precedence: nil, + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 3134, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.5"}, + Precedence: ptr.To(255), + }, + { + DatacenterID: 3133, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.4"}, + Precedence: nil, + }, }, }, + DomainName: "example.akadns.net", }, - domain: "example.akadns.net", headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, - responseStatus: http.StatusCreated, + responseStatus: http.StatusOK, responseBody: ` { "resource": { @@ -845,39 +863,90 @@ func TestGTM_UpdateProperty(t *testing.T) { } } `, - expectedResponse: &ResponseStatus{ - ChangeID: "eee0c3b4-0e45-4f4b-822c-7dbc60764d18", - Message: "Change Pending", - PassingValidation: true, - PropagationStatus: "PENDING", - PropagationStatusDate: "2014-04-15T11:30:27.000+0000", - Links: &[]Link{ - { - Href: "/config-gtm/v1/domains/example.akadns.net/status/current", - Rel: "self", + expectedResponse: &UpdatePropertyResponse{ + Resource: &Property{ + BalanceByDownloadScore: false, + HandoutMode: "normal", + IPv6: false, + Name: "origin", + ScoreAggregationType: "mean", + StaticTTL: 600, + DynamicTTL: 300, + Type: "weighted-round-robin", + UseComputedTargets: false, + LivenessTests: []LivenessTest{ + { + DisableNonstandardPortWarning: false, + HTTPError3xx: true, + HTTPError4xx: true, + HTTPError5xx: true, + Name: "health-check", + TestInterval: 60, + TestObject: "/status", + TestObjectPort: 80, + TestObjectProtocol: "HTTP", + TestTimeout: 25.0, + }, + }, + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 3134, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.5"}, + Precedence: ptr.To(255), + }, + { + DatacenterID: 3133, + Enabled: true, + Weight: 50.0, + Servers: []string{"1.2.3.4"}, + }, + }, + Links: []Link{ + { + Href: "/config-gtm/v1/domains/example.akadns.net/properties/origin", + Rel: "self", + }, + }, + }, + Status: &ResponseStatus{ + ChangeID: "eee0c3b4-0e45-4f4b-822c-7dbc60764d18", + Message: "Change Pending", + PassingValidation: true, + PropagationStatus: "PENDING", + PropagationStatusDate: "2014-04-15T11:30:27.000+0000", + Links: []Link{ + { + Href: "/config-gtm/v1/domains/example.akadns.net/status/current", + Rel: "self", + }, }, }, }, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/origin", }, "validation error - missing precedence for ranked-failover property type": { - property: &Property{ - Type: "ranked-failover", - Name: "property", - HandoutMode: "normal", - ScoreAggregationType: "mean", - TrafficTargets: []*TrafficTarget{ - { - DatacenterID: 1, - Enabled: false, - Precedence: nil, - }, - { - DatacenterID: 2, - Enabled: false, - Precedence: nil, + params: UpdatePropertyRequest{ + Property: &Property{ + Type: "ranked-failover", + Name: "property", + HandoutMode: "normal", + ScoreAggregationType: "mean", + TrafficTargets: []TrafficTarget{ + { + DatacenterID: 1, + Enabled: false, + Precedence: nil, + }, + { + DatacenterID: 2, + Enabled: false, + Precedence: nil, + }, }, }, + DomainName: "example.akadns.net", }, withError: true, assertError: func(t *testing.T, err error) { @@ -885,25 +954,30 @@ func TestGTM_UpdateProperty(t *testing.T) { }, }, "validation error - no traffic targets": { - property: &Property{ - Type: "ranked-failover", - Name: "property", - HandoutMode: "normal", - ScoreAggregationType: "mean", + params: UpdatePropertyRequest{ + Property: &Property{ + Type: "ranked-failover", + Name: "property", + HandoutMode: "normal", + ScoreAggregationType: "mean", + }, + DomainName: "example.akadns.net", }, withError: true, assertError: func(t *testing.T, err error) { - assert.ErrorContains(t, err, "property validation failed. TrafficTargets: no traffic targets are enabled") + assert.ErrorContains(t, err, "TrafficTargets: no traffic targets are enabled") }, }, "500 internal server error": { - property: &Property{ - Name: "testName", - HandoutMode: "normal", - ScoreAggregationType: "mean", - Type: "failover", + params: UpdatePropertyRequest{ + Property: &Property{ + Name: "testName", + HandoutMode: "normal", + ScoreAggregationType: "mean", + Type: "failover", + }, + DomainName: "example.akadns.net", }, - domain: "example.akadns.net", responseStatus: http.StatusInternalServerError, responseBody: ` { @@ -911,7 +985,7 @@ func TestGTM_UpdateProperty(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating zone" }`, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/testName", withError: true, assertError: func(t *testing.T, err error) { want := &Error{ @@ -928,6 +1002,7 @@ func TestGTM_UpdateProperty(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -939,7 +1014,7 @@ func TestGTM_UpdateProperty(t *testing.T) { result, err := client.UpdateProperty( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.property, test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError { test.assertError(t, err) return @@ -951,7 +1026,7 @@ func TestGTM_UpdateProperty(t *testing.T) { } func TestGTM_DeleteProperty(t *testing.T) { - var result PropertyResponse + var result DeletePropertyResponse var req Property respData, err := loadTestData("TestGTM_CreateProperty.resp.json") @@ -973,29 +1048,32 @@ func TestGTM_DeleteProperty(t *testing.T) { } tests := map[string]struct { - prop *Property - domain string + params DeletePropertyRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *ResponseStatus + expectedResponse *DeletePropertyResponse withError error headers http.Header }{ "200 Success": { - prop: &req, - domain: "example.akadns.net", + params: DeletePropertyRequest{ + PropertyName: "www", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: respData, - expectedResponse: result.Status, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedResponse: &result, + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/www", }, "500 internal server error": { - prop: &req, - domain: "example.akadns.net", + params: DeletePropertyRequest{ + PropertyName: "www", + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -1003,7 +1081,7 @@ func TestGTM_DeleteProperty(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating zone" }`), - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/properties/www", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -1016,6 +1094,7 @@ func TestGTM_DeleteProperty(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodDelete, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -1027,7 +1106,7 @@ func TestGTM_DeleteProperty(t *testing.T) { result, err := client.DeleteProperty( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.prop, test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/resource.go b/pkg/gtm/resource.go index 92a7d8c9..b1a7b91a 100644 --- a/pkg/gtm/resource.go +++ b/pkg/gtm/resource.go @@ -2,81 +2,166 @@ package gtm import ( "context" + "errors" "fmt" "net/http" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // ResourceInstance contains information about the resources that constrain the properties within the data center + ResourceInstance struct { + DatacenterID int `json:"datacenterId"` + UseDefaultLoadObject bool `json:"useDefaultLoadObject"` + LoadObject + } + + // Resource represents a GTM resource + Resource struct { + Type string `json:"type"` + HostHeader string `json:"hostHeader,omitempty"` + LeastSquaresDecay float64 `json:"leastSquaresDecay,omitempty"` + Description string `json:"description,omitempty"` + LeaderString string `json:"leaderString,omitempty"` + ConstrainedProperty string `json:"constrainedProperty,omitempty"` + ResourceInstances []ResourceInstance `json:"resourceInstances,omitempty"` + AggregationType string `json:"aggregationType,omitempty"` + Links []Link `json:"links,omitempty"` + LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` + UpperBound int `json:"upperBound,omitempty"` + Name string `json:"name"` + MaxUMultiplicativeIncrement float64 `json:"maxUMultiplicativeIncrement,omitempty"` + DecayRate float64 `json:"decayRate,omitempty"` + } + + // ResourceList is the structure returned by List Resources + ResourceList struct { + ResourceItems []Resource `json:"items"` + } + + // ListResourcesRequest contains request parameters for ListResources + ListResourcesRequest struct { + DomainName string + } + + // GetResourceRequest contains request parameters for GetResource + GetResourceRequest struct { + DomainName string + ResourceName string + } + + // GetResourceResponse contains the response data from GetResource operation + GetResourceResponse Resource + + // ResourceRequest contains request parameters + ResourceRequest struct { + Resource *Resource + DomainName string + } + + // CreateResourceRequest contains request parameters for CreateResource + CreateResourceRequest ResourceRequest + + // CreateResourceResponse contains the response data from CreateResource operation + CreateResourceResponse struct { + Resource *Resource `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // UpdateResourceRequest contains request parameters for UpdateResource + UpdateResourceRequest ResourceRequest + + // UpdateResourceResponse contains the response data from UpdateResource operation + UpdateResourceResponse struct { + Resource *Resource `json:"resource"` + Status *ResponseStatus `json:"status"` + } + + // DeleteResourceRequest contains request parameters for DeleteResource + DeleteResourceRequest struct { + DomainName string + ResourceName string + } + + // DeleteResourceResponse contains the response data from DeleteResource operation + DeleteResourceResponse struct { + Resource *Resource `json:"resource"` + Status *ResponseStatus `json:"status"` + } ) -// Resources contains operations available on a Resource resource. -type Resources interface { - // ListResources retrieves all Resources - // - // See: https://techdocs.akamai.com/gtm/reference/get-resources - ListResources(context.Context, string) ([]*Resource, error) - // GetResource retrieves a Resource with the given name. - // - // See: https://techdocs.akamai.com/gtm/reference/get-resource - GetResource(context.Context, string, string) (*Resource, error) - // CreateResource creates the datacenter identified by the receiver argument in the specified domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-resource - CreateResource(context.Context, *Resource, string) (*ResourceResponse, error) - // DeleteResource deletes the datacenter identified by the receiver argument from the domain specified. - // - // See: https://techdocs.akamai.com/gtm/reference/delete-resource - DeleteResource(context.Context, *Resource, string) (*ResponseStatus, error) - // UpdateResource updates the datacenter identified in the receiver argument in the provided domain. - // - // See: https://techdocs.akamai.com/gtm/reference/put-resource - UpdateResource(context.Context, *Resource, string) (*ResponseStatus, error) +var ( + // ErrListResources is returned when ListResources fails + ErrListResources = errors.New("list resources") + // ErrGetResource is returned when GetResource fails + ErrGetResource = errors.New("get resource") + // ErrCreateResource is returned when CreateResource fails + ErrCreateResource = errors.New("create resource") + // ErrUpdateResource is returned when UpdateResource fails + ErrUpdateResource = errors.New("update resource") + // ErrDeleteResource is returned when DeleteResource fails + ErrDeleteResource = errors.New("delete resource") +) + +// Validate validates ListResourcesRequest +func (r ListResourcesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + }) +} + +// Validate validates GetResourceRequest +func (r GetResourceRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "ResourceName": validation.Validate(r.ResourceName, validation.Required), + }) } -// ResourceInstance contains information about the resources that constrain the properties within the data center -type ResourceInstance struct { - DatacenterID int `json:"datacenterId"` - UseDefaultLoadObject bool `json:"useDefaultLoadObject"` - LoadObject +// Validate validates CreateResourceRequest +func (r CreateResourceRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "Resource": validation.Validate(r.Resource, validation.Required), + }) } -// Resource represents a GTM resource -type Resource struct { - Type string `json:"type"` - HostHeader string `json:"hostHeader,omitempty"` - LeastSquaresDecay float64 `json:"leastSquaresDecay,omitempty"` - Description string `json:"description,omitempty"` - LeaderString string `json:"leaderString,omitempty"` - ConstrainedProperty string `json:"constrainedProperty,omitempty"` - ResourceInstances []*ResourceInstance `json:"resourceInstances,omitempty"` - AggregationType string `json:"aggregationType,omitempty"` - Links []*Link `json:"links,omitempty"` - LoadImbalancePercentage float64 `json:"loadImbalancePercentage,omitempty"` - UpperBound int `json:"upperBound,omitempty"` - Name string `json:"name"` - MaxUMultiplicativeIncrement float64 `json:"maxUMultiplicativeIncrement,omitempty"` - DecayRate float64 `json:"decayRate,omitempty"` +// Validate validates UpdateResourceRequest +func (r UpdateResourceRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "Resource": validation.Validate(r.Resource, validation.Required), + }) } -// ResourceList is the structure returned by List Resources -type ResourceList struct { - ResourceItems []*Resource `json:"items"` +// Validate validates DeleteResourceRequest +func (r DeleteResourceRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DomainName": validation.Validate(r.DomainName, validation.Required), + "ResourceName": validation.Validate(r.ResourceName, validation.Required), + }) } // Validate validates Resource func (r *Resource) Validate() error { - if len(r.Name) < 1 { - return fmt.Errorf("resource is missing Name") - } - if len(r.Type) < 1 { - return fmt.Errorf("resource is missing Type") - } - - return nil + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Name": validation.Validate(r.Name, validation.Required), + "Type": validation.Validate(r.Type, validation.Required), + "AggregationType": validation.Validate(r.AggregationType, validation.Required), + }) } -func (g *gtm) ListResources(ctx context.Context, domainName string) ([]*Resource, error) { +func (g *gtm) ListResources(ctx context.Context, params ListResourcesRequest) ([]Resource, error) { logger := g.Log(ctx) logger.Debug("ListResources") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources", domainName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrListResources, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources", params.DomainName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create ListResources request: %w", err) @@ -96,18 +181,22 @@ func (g *gtm) ListResources(ctx context.Context, domainName string) ([]*Resource return result.ResourceItems, nil } -func (g *gtm) GetResource(ctx context.Context, resourceName, domainName string) (*Resource, error) { +func (g *gtm) GetResource(ctx context.Context, params GetResourceRequest) (*GetResourceResponse, error) { logger := g.Log(ctx) logger.Debug("GetResource") - getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", domainName, resourceName) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetResource, ErrStructValidation, err) + } + + getURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", params.DomainName, params.ResourceName) req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) if err != nil { return nil, fmt.Errorf("failed to create GetResource request: %w", err) } setVersionHeader(req, schemaVersion) - var result Resource + var result GetResourceResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("GetResource request failed: %w", err) @@ -120,67 +209,78 @@ func (g *gtm) GetResource(ctx context.Context, resourceName, domainName string) return &result, nil } -func (g *gtm) CreateResource(ctx context.Context, resource *Resource, domainName string) (*ResourceResponse, error) { +func (g *gtm) CreateResource(ctx context.Context, params CreateResourceRequest) (*CreateResourceResponse, error) { logger := g.Log(ctx) logger.Debug("CreateResource") - return resource.save(ctx, g, domainName) -} + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateResource, ErrStructValidation, err) + } -func (g *gtm) UpdateResource(ctx context.Context, resource *Resource, domainName string) (*ResponseStatus, error) { - logger := g.Log(ctx) - logger.Debug("UpdateResource") + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", params.DomainName, params.Resource.Name) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create Resource request: %w", err) + } + setVersionHeader(req, schemaVersion) - stat, err := resource.save(ctx, g, domainName) + var result CreateResourceResponse + resp, err := g.Exec(req, &result, params.Resource) if err != nil { - return nil, err + return nil, fmt.Errorf("resource request failed: %w", err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, g.Error(resp) } - return stat.Status, err + + return &result, nil } -// save is a function that saves Resource in given domain. Common path for Create and Update. -func (r *Resource) save(ctx context.Context, g *gtm, domainName string) (*ResourceResponse, error) { - if err := r.Validate(); err != nil { - return nil, fmt.Errorf("resource validation failed. %w", err) +func (g *gtm) UpdateResource(ctx context.Context, params UpdateResourceRequest) (*UpdateResourceResponse, error) { + logger := g.Log(ctx) + logger.Debug("UpdateResource") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateResource, ErrStructValidation, err) } - putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", domainName, r.Name) + putURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", params.DomainName, params.Resource.Name) req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Resource request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResourceResponse - resp, err := g.Exec(req, &result, r) + var result UpdateResourceResponse + resp, err := g.Exec(req, &result, params.Resource) if err != nil { return nil, fmt.Errorf("resource request failed: %w", err) } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusOK { return nil, g.Error(resp) } return &result, nil } -func (g *gtm) DeleteResource(ctx context.Context, resource *Resource, domainName string) (*ResponseStatus, error) { +func (g *gtm) DeleteResource(ctx context.Context, params DeleteResourceRequest) (*DeleteResourceResponse, error) { logger := g.Log(ctx) logger.Debug("DeleteResource") - if err := resource.Validate(); err != nil { - logger.Errorf("Resource validation failed. %w", err) - return nil, fmt.Errorf("DeleteResource validation failed. %w", err) + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrDeleteResource, ErrStructValidation, err) } - delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", domainName, resource.Name) + delURL := fmt.Sprintf("/config-gtm/v1/domains/%s/resources/%s", params.DomainName, params.ResourceName) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, delURL, nil) if err != nil { return nil, fmt.Errorf("failed to create Delete request: %w", err) } setVersionHeader(req, schemaVersion) - var result ResponseBody + var result DeleteResourceResponse resp, err := g.Exec(req, &result) if err != nil { return nil, fmt.Errorf("DeleteResource request failed: %w", err) @@ -190,5 +290,5 @@ func (g *gtm) DeleteResource(ctx context.Context, resource *Resource, domainName return nil, g.Error(resp) } - return result.Status, nil + return &result, nil } diff --git a/pkg/gtm/resource_test.go b/pkg/gtm/resource_test.go index 56bae885..c634355b 100644 --- a/pkg/gtm/resource_test.go +++ b/pkg/gtm/resource_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,16 +27,18 @@ func TestGTM_ListResources(t *testing.T) { } tests := map[string]struct { - domain string + params ListResourcesRequest responseStatus int responseBody string expectedPath string - expectedResponse []*Resource + expectedResponse []Resource withError error headers http.Header }{ "200 OK": { - domain: "example.akadns.net", + params: ListResourcesRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, @@ -46,7 +48,9 @@ func TestGTM_ListResources(t *testing.T) { expectedResponse: result.ResourceItems, }, "500 internal server error": { - domain: "example.akadns.net", + params: ListResourcesRequest{ + DomainName: "example.akadns.net", + }, headers: http.Header{}, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -79,7 +83,7 @@ func TestGTM_ListResources(t *testing.T) { result, err := client.ListResources( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -91,7 +95,7 @@ func TestGTM_ListResources(t *testing.T) { } func TestGTM_GetResource(t *testing.T) { - var result Resource + var result GetResourceResponse respData, err := loadTestData("TestGTM_GetResource.resp.json") if err != nil { @@ -103,25 +107,28 @@ func TestGTM_GetResource(t *testing.T) { } tests := map[string]struct { - name string - domain string + params GetResourceRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *Resource + expectedResponse *GetResourceResponse withError error }{ "200 OK": { - name: "www", - domain: "example.akadns.net", + params: GetResourceRequest{ + ResourceName: "www", + DomainName: "example.akadns.net", + }, responseStatus: http.StatusOK, responseBody: respData, expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/www", expectedResponse: &result, }, "500 internal server error": { - name: "www", - domain: "example.akadns.net", + params: GetResourceRequest{ + ResourceName: "www", + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -149,7 +156,7 @@ func TestGTM_GetResource(t *testing.T) { assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetResource(context.Background(), test.name, test.domain) + result, err := client.GetResource(context.Background(), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -161,7 +168,7 @@ func TestGTM_GetResource(t *testing.T) { } func TestGTM_CreateResource(t *testing.T) { - var result ResourceResponse + var result CreateResourceResponse var req Resource respData, err := loadTestData("TestGTM_CreateResource.resp.json") @@ -183,29 +190,32 @@ func TestGTM_CreateResource(t *testing.T) { } tests := map[string]struct { - domain string - prop *Resource + params CreateResourceRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *ResourceResponse + expectedResponse *CreateResourceResponse withError error headers http.Header }{ "201 Created": { - prop: &req, - domain: "example.akadns.net", + params: CreateResourceRequest{ + Resource: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusCreated, responseBody: respData, expectedResponse: &result, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/origin", }, "500 internal server error": { - prop: &req, - domain: "example.akadns.net", + params: CreateResourceRequest{ + Resource: &req, + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -213,7 +223,7 @@ func TestGTM_CreateResource(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating domain" }`), - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/origin", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -226,6 +236,7 @@ func TestGTM_CreateResource(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -237,7 +248,7 @@ func TestGTM_CreateResource(t *testing.T) { result, err := client.CreateResource( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.prop, test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -249,7 +260,7 @@ func TestGTM_CreateResource(t *testing.T) { } func TestGTM_UpdateResource(t *testing.T) { - var result ResourceResponse + var result UpdateResourceResponse var req Resource respData, err := loadTestData("TestGTM_CreateResource.resp.json") @@ -271,29 +282,32 @@ func TestGTM_UpdateResource(t *testing.T) { } tests := map[string]struct { - prop *Resource - domain string + params UpdateResourceRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *ResponseStatus + expectedResponse *UpdateResourceResponse withError error headers http.Header }{ "200 Success": { - prop: &req, - domain: "example.akadns.net", + params: UpdateResourceRequest{ + Resource: &req, + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, - responseStatus: http.StatusCreated, + responseStatus: http.StatusOK, responseBody: respData, - expectedResponse: result.Status, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedResponse: &result, + expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/origin", }, "500 internal server error": { - prop: &req, - domain: "example.akadns.net", + params: UpdateResourceRequest{ + Resource: &req, + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -301,7 +315,7 @@ func TestGTM_UpdateResource(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating zone" }`), - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/origin", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -314,6 +328,7 @@ func TestGTM_UpdateResource(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -325,7 +340,7 @@ func TestGTM_UpdateResource(t *testing.T) { result, err := client.UpdateResource( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.prop, test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return @@ -337,7 +352,7 @@ func TestGTM_UpdateResource(t *testing.T) { } func TestGTM_DeleteResource(t *testing.T) { - var result ResourceResponse + var result DeleteResourceResponse var req Resource respData, err := loadTestData("TestGTM_CreateResource.resp.json") @@ -359,29 +374,32 @@ func TestGTM_DeleteResource(t *testing.T) { } tests := map[string]struct { - prop *Resource - domain string + params DeleteResourceRequest responseStatus int responseBody []byte expectedPath string - expectedResponse *ResponseStatus + expectedResponse *DeleteResourceResponse withError error headers http.Header }{ "200 Success": { - prop: &req, - domain: "example.akadns.net", + params: DeleteResourceRequest{ + ResourceName: "www", + DomainName: "example.akadns.net", + }, headers: http.Header{ "Content-Type": []string{"application/vnd.config-gtm.v1.4+json;charset=UTF-8"}, }, responseStatus: http.StatusOK, responseBody: respData, - expectedResponse: result.Status, - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedResponse: &result, + expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/www", }, "500 internal server error": { - prop: &req, - domain: "example.akadns.net", + params: DeleteResourceRequest{ + ResourceName: "www", + DomainName: "example.akadns.net", + }, responseStatus: http.StatusInternalServerError, responseBody: []byte(` { @@ -389,7 +407,7 @@ func TestGTM_DeleteResource(t *testing.T) { "title": "Internal Server Error", "detail": "Error creating zone" }`), - expectedPath: "/config-gtm/v1/domains/example.akadns.net?contractId=1-2ABCDE", + expectedPath: "/config-gtm/v1/domains/example.akadns.net/resources/www", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -402,6 +420,7 @@ func TestGTM_DeleteResource(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodDelete, r.Method) w.WriteHeader(test.responseStatus) if len(test.responseBody) > 0 { @@ -413,7 +432,7 @@ func TestGTM_DeleteResource(t *testing.T) { result, err := client.DeleteResource( session.ContextWithOptions( context.Background(), - session.WithContextHeaders(test.headers)), test.prop, test.domain) + session.WithContextHeaders(test.headers)), test.params) if test.withError != nil { assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) return diff --git a/pkg/gtm/testdata/TestGTM_CreateResource.req.json b/pkg/gtm/testdata/TestGTM_CreateResource.req.json index 6f4727ce..2433f2dd 100644 --- a/pkg/gtm/testdata/TestGTM_CreateResource.req.json +++ b/pkg/gtm/testdata/TestGTM_CreateResource.req.json @@ -8,7 +8,7 @@ "healthThreshold": null, "ipv6": false, "name": "origin", - "scoreAggregationType": "mean", + "aggregationType": "mean", "staticTTL": 600, "stickinessBonusConstant": 0, "stickinessBonusPercentage": 0, diff --git a/pkg/hapi/change_requests.go b/pkg/hapi/change_requests.go index a913a16f..5ff77fd4 100644 --- a/pkg/hapi/change_requests.go +++ b/pkg/hapi/change_requests.go @@ -8,15 +8,6 @@ import ( ) type ( - // ChangeRequests contains operations to query for Change Requests. - ChangeRequests interface { - // GetChangeRequest request status and details specified by the change ID - // that is provided when you make a change request. - // - // See: https://techdocs.akamai.com/edge-hostnames/reference/get-changeid - GetChangeRequest(context.Context, GetChangeRequest) (*ChangeRequest, error) - } - // GetChangeRequest is a request struct GetChangeRequest struct { ChangeID int diff --git a/pkg/hapi/edgehostname.go b/pkg/hapi/edgehostname.go index 738fed8c..473b810d 100644 --- a/pkg/hapi/edgehostname.go +++ b/pkg/hapi/edgehostname.go @@ -11,38 +11,12 @@ import ( "strings" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // EdgeHostnames contains operations available on Edge Hostname resource. - EdgeHostnames interface { - // DeleteEdgeHostname allows deleting a specific edge hostname. - // You must have an Admin or Technical role in order to delete an edge hostname. - // You can delete any hostname that’s not currently part of an active Property Manager configuration. - // - // See: https://techdocs.akamai.com/edge-hostnames/reference/delete-edgehostname - DeleteEdgeHostname(context.Context, DeleteEdgeHostnameRequest) (*DeleteEdgeHostnameResponse, error) - - // GetEdgeHostname gets a specific edge hostname's details including its product ID, IP version behavior, - // and China CDN or Edge IP Binding status. - // - // See: https://techdocs.akamai.com/edge-hostnames/reference/get-edgehostnameid - GetEdgeHostname(context.Context, int) (*GetEdgeHostnameResponse, error) - - // UpdateEdgeHostname allows update ttl (path = "/ttl") or IpVersionBehaviour (path = "/ipVersionBehavior") - // - // See: https://techdocs.akamai.com/edge-hostnames/reference/patch-edgehostnames - UpdateEdgeHostname(context.Context, UpdateEdgeHostnameRequest) (*UpdateEdgeHostnameResponse, error) - - // GetCertificate gets the certificate associated with an enhanced TLS edge hostname - // - // See: https://techdocs.akamai.com/edge-hostnames/reference/get-edge-hostname-certificate - GetCertificate(context.Context, GetCertificateRequest) (*GetCertificateResponse, error) - } - // DeleteEdgeHostnameRequest is used to delete edge hostname DeleteEdgeHostnameRequest struct { DNSZone string diff --git a/pkg/hapi/edgehostname_test.go b/pkg/hapi/edgehostname_test.go index e3030aed..5f95851a 100644 --- a/pkg/hapi/edgehostname_test.go +++ b/pkg/hapi/edgehostname_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/hapi/errors.go b/pkg/hapi/errors.go index 9d0f159c..42e9cb09 100644 --- a/pkg/hapi/errors.go +++ b/pkg/hapi/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/hapi/errors_test.go b/pkg/hapi/errors_test.go index 5145e184..24d5a408 100644 --- a/pkg/hapi/errors_test.go +++ b/pkg/hapi/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/hapi/hapi.go b/pkg/hapi/hapi.go index d3959ac0..2868f6d0 100644 --- a/pkg/hapi/hapi.go +++ b/pkg/hapi/hapi.go @@ -4,9 +4,10 @@ package hapi import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -17,8 +18,38 @@ var ( type ( // HAPI is the hapi api interface HAPI interface { - ChangeRequests - EdgeHostnames + // ChangeRequests + + // GetChangeRequest request status and details specified by the change ID + // that is provided when you make a change request. + // + // See: https://techdocs.akamai.com/edge-hostnames/reference/get-changeid + GetChangeRequest(context.Context, GetChangeRequest) (*ChangeRequest, error) + + // EdgeHostnames + + // DeleteEdgeHostname allows deleting a specific edge hostname. + // You must have an Admin or Technical role in order to delete an edge hostname. + // You can delete any hostname that’s not currently part of an active Property Manager configuration. + // + // See: https://techdocs.akamai.com/edge-hostnames/reference/delete-edgehostname + DeleteEdgeHostname(context.Context, DeleteEdgeHostnameRequest) (*DeleteEdgeHostnameResponse, error) + + // GetEdgeHostname gets a specific edge hostname's details including its product ID, IP version behavior, + // and China CDN or Edge IP Binding status. + // + // See: https://techdocs.akamai.com/edge-hostnames/reference/get-edgehostnameid + GetEdgeHostname(context.Context, int) (*GetEdgeHostnameResponse, error) + + // UpdateEdgeHostname allows update ttl (path = "/ttl") or IpVersionBehaviour (path = "/ipVersionBehavior") + // + // See: https://techdocs.akamai.com/edge-hostnames/reference/patch-edgehostnames + UpdateEdgeHostname(context.Context, UpdateEdgeHostnameRequest) (*UpdateEdgeHostnameResponse, error) + + // GetCertificate gets the certificate associated with an enhanced TLS edge hostname + // + // See: https://techdocs.akamai.com/edge-hostnames/reference/get-edge-hostname-certificate + GetCertificate(context.Context, GetCertificateRequest) (*GetCertificateResponse, error) } hapi struct { diff --git a/pkg/hapi/hapi_test.go b/pkg/hapi/hapi_test.go index 723a152f..69ef19ed 100644 --- a/pkg/hapi/hapi_test.go +++ b/pkg/hapi/hapi_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/iam/api_clients.go b/pkg/iam/api_clients.go new file mode 100644 index 00000000..816d93c1 --- /dev/null +++ b/pkg/iam/api_clients.go @@ -0,0 +1,606 @@ +package iam + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // LockAPIClientRequest contains the request parameters for the LockAPIClient endpoint. + LockAPIClientRequest struct { + ClientID string + } + + // UnlockAPIClientRequest contains the request parameters for the UnlockAPIClient endpoint. + UnlockAPIClientRequest struct { + ClientID string + } + + // LockAPIClientResponse holds the response data from LockAPIClient. + LockAPIClientResponse APIClient + + // UnlockAPIClientResponse holds the response data from UnlockAPIClient. + UnlockAPIClientResponse APIClient + + // APIClient contains information about the API client. + APIClient struct { + AccessToken string `json:"accessToken"` + ActiveCredentialCount int64 `json:"activeCredentialCount"` + AllowAccountSwitch bool `json:"allowAccountSwitch"` + AuthorizedUsers []string `json:"authorizedUsers"` + CanAutoCreateCredential bool `json:"canAutoCreateCredential"` + ClientDescription string `json:"clientDescription"` + ClientID string `json:"clientId"` + ClientName string `json:"clientName"` + ClientType ClientType `json:"clientType"` + CreatedBy string `json:"createdBy"` + CreatedDate time.Time `json:"createdDate"` + IsLocked bool `json:"isLocked"` + NotificationEmails []string `json:"notificationEmails"` + ServiceConsumerToken string `json:"serviceConsumerToken"` + } + + // ListAPIClientsRequest contains the request parameters for the ListAPIClients endpoint. + ListAPIClientsRequest struct { + Actions bool + } + + // ListAPIClientsResponse describes the response of the ListAPIClients endpoint. + ListAPIClientsResponse []ListAPIClientsItem + + // ListAPIClientsItem represents information returned by the ListAPIClients endpoint for a single API client. + ListAPIClientsItem struct { + AccessToken string `json:"accessToken"` + Actions *ListAPIClientsActions `json:"actions"` + ActiveCredentialCount int64 `json:"activeCredentialCount"` + AllowAccountSwitch bool `json:"allowAccountSwitch"` + AuthorizedUsers []string `json:"authorizedUsers"` + CanAutoCreateCredential bool `json:"canAutoCreateCredential"` + ClientDescription string `json:"clientDescription"` + ClientID string `json:"clientId"` + ClientName string `json:"clientName"` + ClientType ClientType `json:"clientType"` + CreatedBy string `json:"createdBy"` + CreatedDate time.Time `json:"createdDate"` + IsLocked bool `json:"isLocked"` + NotificationEmails []string `json:"notificationEmails"` + ServiceConsumerToken string `json:"serviceConsumerToken"` + } + + // ListAPIClientsActions specifies activities available for the API client. + ListAPIClientsActions struct { + Delete bool `json:"delete"` + DeactivateAll bool `json:"deactivateAll"` + Edit bool `json:"edit"` + Lock bool `json:"lock"` + Transfer bool `json:"transfer"` + Unlock bool `json:"unlock"` + } + + // GetAPIClientRequest contains the request parameters for the GetAPIClient endpoint. + GetAPIClientRequest struct { + ClientID string + Actions bool + GroupAccess bool + APIAccess bool + Credentials bool + IPACL bool + } + + // CreateAPIClientResponse describes the response of the CreateAPIClient endpoint. + CreateAPIClientResponse struct { + AccessToken string `json:"accessToken"` + Actions *APIClientActions `json:"actions"` + ActiveCredentialCount int64 `json:"activeCredentialCount"` + AllowAccountSwitch bool `json:"allowAccountSwitch"` + APIAccess APIAccess `json:"apiAccess"` + AuthorizedUsers []string `json:"authorizedUsers"` + BaseURL string `json:"baseURL"` + CanAutoCreateCredential bool `json:"canAutoCreateCredential"` + ClientDescription string `json:"clientDescription"` + ClientID string `json:"clientId"` + ClientName string `json:"clientName"` + ClientType ClientType `json:"clientType"` + CreatedBy string `json:"createdBy"` + CreatedDate time.Time `json:"createdDate"` + Credentials []CreateAPIClientCredential `json:"credentials"` + GroupAccess GroupAccess `json:"groupAccess"` + IPACL IPACL `json:"ipAcl"` + IsLocked bool `json:"isLocked"` + NotificationEmails []string `json:"notificationEmails"` + PurgeOptions PurgeOptions `json:"purgeOptions"` + ServiceProviderID int64 `json:"serviceProviderId"` + } + + // GetAPIClientResponse describes the response of the GetAPIClient endpoint. + GetAPIClientResponse struct { + AccessToken string `json:"accessToken"` + Actions *APIClientActions `json:"actions"` + ActiveCredentialCount int64 `json:"activeCredentialCount"` + AllowAccountSwitch bool `json:"allowAccountSwitch"` + APIAccess APIAccess `json:"apiAccess"` + AuthorizedUsers []string `json:"authorizedUsers"` + BaseURL string `json:"baseURL"` + CanAutoCreateCredential bool `json:"canAutoCreateCredential"` + ClientDescription string `json:"clientDescription"` + ClientID string `json:"clientId"` + ClientName string `json:"clientName"` + ClientType ClientType `json:"clientType"` + CreatedBy string `json:"createdBy"` + CreatedDate time.Time `json:"createdDate"` + Credentials []APIClientCredential `json:"credentials"` + GroupAccess GroupAccess `json:"groupAccess"` + IPACL IPACL `json:"ipAcl"` + IsLocked bool `json:"isLocked"` + NotificationEmails []string `json:"notificationEmails"` + PurgeOptions PurgeOptions `json:"purgeOptions"` + ServiceProviderID int64 `json:"serviceProviderId"` + } + + // APIClientActions specifies activities available for the API client. + APIClientActions struct { + Delete bool `json:"delete"` + DeactivateAll bool `json:"deactivateAll"` + Edit bool `json:"edit"` + EditAPIs bool `json:"editApis"` + EditAuth bool `json:"editAuth"` + EditGroups bool `json:"editGroups"` + EditIPAcl bool `json:"editIpAcl"` + EditSwitchAccount bool `json:"editSwitchAccount"` + Lock bool `json:"lock"` + Transfer bool `json:"transfer"` + Unlock bool `json:"unlock"` + } + + // APIAccess represents the APIs the API client can access. + APIAccess struct { + AllAccessibleAPIs bool `json:"allAccessibleApis"` + APIs []API `json:"apis"` + } + + // API represents single Application Programming Interface (API). + API struct { + AccessLevel AccessLevel `json:"accessLevel"` + APIID int64 `json:"apiId"` + APIName string `json:"apiName"` + Description string `json:"description"` + DocumentationURL string `json:"documentationUrl"` + Endpoint string `json:"endPoint"` + } + + // APIClientCredential represents single Credential returned by APIClient interfaces. + APIClientCredential struct { + Actions CredentialActions `json:"actions"` + ClientToken string `json:"clientToken"` + CreatedOn time.Time `json:"createdOn"` + CredentialID int64 `json:"credentialId"` + Description string `json:"description"` + ExpiresOn time.Time `json:"expiresOn"` + Status CredentialStatus `json:"status"` + } + + // CreateAPIClientCredential represents single Credential returned by CreateAPIClient endpoint. + CreateAPIClientCredential struct { + Actions CredentialActions `json:"actions"` + ClientToken string `json:"clientToken"` + ClientSecret string `json:"clientSecret"` + CreatedOn time.Time `json:"createdOn"` + CredentialID int64 `json:"credentialId"` + Description string `json:"description"` + ExpiresOn time.Time `json:"expiresOn"` + Status CredentialStatus `json:"status"` + } + + // GroupAccess specifies the API client's group access. + GroupAccess struct { + CloneAuthorizedUserGroups bool `json:"cloneAuthorizedUserGroups"` + Groups []ClientGroup `json:"groups"` + } + + // ClientGroup represents a group the API client can access. + ClientGroup struct { + GroupID int64 `json:"groupId"` + GroupName string `json:"groupName"` + IsBlocked bool `json:"isBlocked"` + ParentGroupID int64 `json:"parentGroupId"` + RoleDescription string `json:"roleDescription"` + RoleID int64 `json:"roleId"` + RoleName string `json:"roleName"` + Subgroups []ClientGroup `json:"subgroups"` + } + + // IPACL specifies the API client's IP list restriction. + IPACL struct { + CIDR []string `json:"cidr"` + Enable bool `json:"enable"` + } + + // PurgeOptions specifies the API clients configuration for access to the Fast Purge API. + PurgeOptions struct { + CanPurgeByCacheTag bool `json:"canPurgeByCacheTag"` + CanPurgeByCPCode bool `json:"canPurgeByCpcode"` + CPCodeAccess CPCodeAccess `json:"cpcodeAccess"` + } + + // CPCodeAccess represents the CP codes the API client can purge. + CPCodeAccess struct { + AllCurrentAndNewCPCodes bool `json:"allCurrentAndNewCpcodes"` + CPCodes []int64 `json:"cpcodes"` + } + + // CreateAPIClientRequest contains the request parameters for the CreateAPIClient endpoint. + CreateAPIClientRequest struct { + AllowAccountSwitch bool `json:"allowAccountSwitch"` + APIAccess APIAccess `json:"apiAccess"` + AuthorizedUsers []string `json:"authorizedUsers"` + CanAutoCreateCredential bool `json:"canAutoCreateCredential"` + ClientDescription string `json:"clientDescription"` + ClientName string `json:"clientName"` + ClientType ClientType `json:"clientType"` + CreateCredential bool `json:"createCredential"` + GroupAccess GroupAccess `json:"groupAccess"` + IPACL *IPACL `json:"ipAcl,omitempty"` + NotificationEmails []string `json:"notificationEmails"` + PurgeOptions *PurgeOptions `json:"purgeOptions,omitempty"` + } + + // UpdateAPIClientRequest contains the request parameters for the UpdateAPIClient endpoint. + UpdateAPIClientRequest struct { + ClientID string + Body UpdateAPIClientRequestBody + } + + // UpdateAPIClientRequestBody represents body params for the UpdateAPIClient endpoint. + UpdateAPIClientRequestBody struct { + AllowAccountSwitch bool `json:"allowAccountSwitch"` + APIAccess APIAccess `json:"apiAccess"` + AuthorizedUsers []string `json:"authorizedUsers"` + CanAutoCreateCredential bool `json:"canAutoCreateCredential"` + ClientDescription string `json:"clientDescription"` + ClientName string `json:"clientName"` + ClientType ClientType `json:"clientType"` + GroupAccess GroupAccess `json:"groupAccess"` + IPACL *IPACL `json:"ipAcl,omitempty"` + NotificationEmails []string `json:"notificationEmails"` + PurgeOptions *PurgeOptions `json:"purgeOptions,omitempty"` + } + + // UpdateAPIClientResponse describes the response from the UpdateAPIClient endpoint. + UpdateAPIClientResponse GetAPIClientResponse + + // DeleteAPIClientRequest contains the request parameters for the DeleteAPIClient endpoint. + DeleteAPIClientRequest struct { + ClientID string + } + + // AccessLevel represents the access level for API. + AccessLevel string +) + +const ( + // ReadWriteLevel is the `READ-WRITE` access level. + ReadWriteLevel AccessLevel = "READ-WRITE" + // ReadOnlyLevel is the `READ-ONLY` access level. + ReadOnlyLevel AccessLevel = "READ-ONLY" +) + +// Validate validates UnlockAPIClientRequest. +func (r UnlockAPIClientRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "ClientID": validation.Validate(r.ClientID, validation.Required), + }) +} + +// Validate validates CreateAPIClientRequest. +func (r CreateAPIClientRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "APIAccess": validation.Validate(r.APIAccess, validation.Required), + "AuthorizedUsers": validation.Validate(r.AuthorizedUsers, validation.Required, validation.Length(1, 0)), + "ClientType": validation.Validate(r.ClientType, validation.Required), + "GroupAccess": validation.Validate(r.GroupAccess, validation.Required), + "PurgeOptions": validation.Validate(r.PurgeOptions), + }) +} + +// Validate validates APIAccess. +func (a APIAccess) Validate() error { + return validation.Errors{ + "APIs": validation.Validate(a.APIs, validation.When(!a.AllAccessibleAPIs, validation.Required)), + }.Filter() +} + +// Validate validates API. +func (a API) Validate() error { + return validation.Errors{ + "AccessLevel": validation.Validate(a.AccessLevel, validation.Required, validation.In(ReadOnlyLevel, ReadWriteLevel).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'READ-ONLY' or 'READ-WRITE'", a.AccessLevel))), + "APIID": validation.Validate(a.APIID, validation.Required), + }.Filter() +} + +// Validate validates GroupAccess. +func (ga GroupAccess) Validate() error { + return validation.Errors{ + "Groups": validation.Validate(ga.Groups, validation.When(!ga.CloneAuthorizedUserGroups, validation.Required)), + }.Filter() +} + +// Validate validates ClientGroup. +func (cg ClientGroup) Validate() error { + return validation.Errors{ + "GroupID": validation.Validate(cg.GroupID, validation.Required), + "RoleID": validation.Validate(cg.RoleID, validation.Required), + }.Filter() +} + +// Validate validates UpdateAPIClientRequest. +func (r UpdateAPIClientRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Body": validation.Validate(r.Body, validation.Required), + }) +} + +// Validate validates UpdateAPIClientRequestBody. +func (r UpdateAPIClientRequestBody) Validate() error { + return validation.Errors{ + "ClientName": validation.Validate(r.ClientName, validation.Required), + "APIAccess": validation.Validate(r.APIAccess, validation.Required), + "AuthorizedUsers": validation.Validate(r.AuthorizedUsers, validation.Required, validation.Length(1, 0)), + "ClientType": validation.Validate(r.ClientType, validation.Required), + "GroupAccess": validation.Validate(r.GroupAccess, validation.Required), + "PurgeOptions": validation.Validate(r.PurgeOptions), + }.Filter() +} + +// Validate validates PurgeOptions. +func (po PurgeOptions) Validate() error { + return validation.Errors{ + "CPCodeAccess": validation.Validate(po.CPCodeAccess), + }.Filter() +} + +// Validate validates CPCodeAccess. +func (ca CPCodeAccess) Validate() error { + return validation.Errors{ + "CPCodes": validation.Validate(ca.CPCodes, validation.When(!ca.AllCurrentAndNewCPCodes, validation.NotNil)), + }.Filter() +} + +var ( + // ErrLockAPIClient is returned when LockAPIClient fails. + ErrLockAPIClient = errors.New("lock api client") + // ErrUnlockAPIClient is returned when UnlockAPIClient fails. + ErrUnlockAPIClient = errors.New("unlock api client") + // ErrListAPIClients is returned when ListAPIClients fails. + ErrListAPIClients = errors.New("list api clients") + // ErrGetAPIClient is returned when GetAPIClient fails. + ErrGetAPIClient = errors.New("get api client") + // ErrCreateAPIClient is returned when CreateAPIClient fails. + ErrCreateAPIClient = errors.New("create api client") + // ErrUpdateAPIClient is returned when UpdateAPIClient fails. + ErrUpdateAPIClient = errors.New("update api client") + // ErrDeleteAPIClient is returned when DeleteAPIClient fails. + ErrDeleteAPIClient = errors.New("delete api client") +) + +func (i *iam) LockAPIClient(ctx context.Context, params LockAPIClientRequest) (*LockAPIClientResponse, error) { + logger := i.Log(ctx) + logger.Debug("LockAPIClient") + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/lock", params.ClientID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrLockAPIClient, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrLockAPIClient, err) + } + + var result LockAPIClientResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrLockAPIClient, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrLockAPIClient, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) UnlockAPIClient(ctx context.Context, params UnlockAPIClientRequest) (*UnlockAPIClientResponse, error) { + logger := i.Log(ctx) + logger.Debug("UnlockAPIClient") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrUnlockAPIClient, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/unlock", params.ClientID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrUnlockAPIClient, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrUnlockAPIClient, err) + } + + var result UnlockAPIClientResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrUnlockAPIClient, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrUnlockAPIClient, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) ListAPIClients(ctx context.Context, params ListAPIClientsRequest) (ListAPIClientsResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListAPIClients") + + uri, err := url.Parse("/identity-management/v3/api-clients") + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAPIClients, err) + } + + q := uri.Query() + q.Add("actions", strconv.FormatBool(params.Actions)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAPIClients, err) + } + + var result ListAPIClientsResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListAPIClients, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListAPIClients, i.Error(resp)) + } + + return result, nil + +} + +func (i *iam) GetAPIClient(ctx context.Context, params GetAPIClientRequest) (*GetAPIClientResponse, error) { + logger := i.Log(ctx) + logger.Debug("GetAPIClient") + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s", params.ClientID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetAPIClient, err) + } + + q := uri.Query() + q.Add("actions", strconv.FormatBool(params.Actions)) + q.Add("groupAccess", strconv.FormatBool(params.GroupAccess)) + q.Add("apiAccess", strconv.FormatBool(params.APIAccess)) + q.Add("credentials", strconv.FormatBool(params.Credentials)) + q.Add("ipAcl", strconv.FormatBool(params.IPACL)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetAPIClient, err) + } + + var result GetAPIClientResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrGetAPIClient, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrGetAPIClient, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) CreateAPIClient(ctx context.Context, params CreateAPIClientRequest) (*CreateAPIClientResponse, error) { + logger := i.Log(ctx) + logger.Debug("CreateAPIClient") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrCreateAPIClient, ErrStructValidation, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/identity-management/v3/api-clients", nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateAPIClient, err) + } + + var result CreateAPIClientResponse + resp, err := i.Exec(req, &result, params) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrCreateAPIClient, err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("%s: %w", ErrCreateAPIClient, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) UpdateAPIClient(ctx context.Context, params UpdateAPIClientRequest) (*UpdateAPIClientResponse, error) { + logger := i.Log(ctx) + logger.Debug("UpdateAPIClient") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateAPIClient, ErrStructValidation, err) + } + + if params.ClientID == "" { + params.ClientID = "self" + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("/identity-management/v3/api-clients/%s", params.ClientID), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateAPIClient, err) + } + + var result UpdateAPIClientResponse + resp, err := i.Exec(req, &result, params.Body) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateAPIClient, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrUpdateAPIClient, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) DeleteAPIClient(ctx context.Context, params DeleteAPIClientRequest) error { + logger := i.Log(ctx) + logger.Debug("DeleteAPIClient") + + if params.ClientID == "" { + params.ClientID = "self" + } + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("/identity-management/v3/api-clients/%s", params.ClientID), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrDeleteAPIClient, err) + } + + resp, err := i.Exec(req, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrDeleteAPIClient, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrDeleteAPIClient, i.Error(resp)) + } + + return nil +} diff --git a/pkg/iam/api_clients_credentials.go b/pkg/iam/api_clients_credentials.go new file mode 100644 index 00000000..47d4b04a --- /dev/null +++ b/pkg/iam/api_clients_credentials.go @@ -0,0 +1,428 @@ +package iam + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // CreateCredentialRequest contains request parameters for the CreateCredential endpoint. + CreateCredentialRequest struct { + ClientID string + } + + // ListCredentialsRequest contains request parameters for the ListCredentials endpoint. + ListCredentialsRequest struct { + ClientID string + Actions bool + } + + // GetCredentialRequest contains request parameters for the GetCredentials endpoint. + GetCredentialRequest struct { + CredentialID int64 + ClientID string + Actions bool + } + + // UpdateCredentialRequest contains request parameters for the UpdateCredential endpoint. + UpdateCredentialRequest struct { + CredentialID int64 + ClientID string + Body UpdateCredentialRequestBody + } + + // UpdateCredentialRequestBody contains request body parameters for the UpdateCredential endpoint. + UpdateCredentialRequestBody struct { + Description string `json:"description,omitempty"` + ExpiresOn time.Time `json:"expiresOn"` + Status CredentialStatus `json:"status"` + } + + // DeleteCredentialRequest contains request parameters for the DeleteCredential endpoint. + DeleteCredentialRequest struct { + CredentialID int64 + ClientID string + } + + // DeactivateCredentialRequest contains request parameters for the DeactivateCredential endpoint. + DeactivateCredentialRequest struct { + CredentialID int64 + ClientID string + } + + // DeactivateCredentialsRequest contains request parameters for the DeactivateCredentials endpoint. + DeactivateCredentialsRequest struct { + ClientID string + } + + // CreateCredentialResponse holds response from the CreateCredentials endpoint. + CreateCredentialResponse struct { + ClientSecret string `json:"clientSecret"` + ClientToken string `json:"clientToken"` + CreatedOn time.Time `json:"createdOn"` + CredentialID int64 `json:"credentialId"` + Description string `json:"description"` + ExpiresOn time.Time `json:"expiresOn"` + Status CredentialStatus `json:"status"` + } + + // ListCredentialsResponse holds response from the ListCredentials endpoint. + ListCredentialsResponse []Credential + + // Credential represents single credential information. + Credential struct { + ClientToken string `json:"clientToken"` + CreatedOn time.Time `json:"createdOn"` + CredentialID int64 `json:"credentialId"` + Description string `json:"description"` + ExpiresOn time.Time `json:"expiresOn"` + Status CredentialStatus `json:"status"` + MaxAllowedExpiry time.Time `json:"maxAllowedExpiry"` + Actions *CredentialActions `json:"actions"` + } + + // CredentialActions describes the actions that can be performed on the credential. + CredentialActions struct { + Deactivate bool `json:"deactivate"` + Delete bool `json:"delete"` + Activate bool `json:"activate"` + EditDescription bool `json:"editDescription"` + EditExpiration bool `json:"editExpiration"` + } + + // GetCredentialResponse holds response from the GetCredential endpoint. + GetCredentialResponse Credential + + // UpdateCredentialResponse holds response from the UpdateCredential endpoint. + UpdateCredentialResponse struct { + Status CredentialStatus `json:"status"` + ExpiresOn time.Time `json:"expiresOn"` + Description *string `json:"description"` + } + + // CredentialStatus represents the status of the credential. + CredentialStatus string +) + +const ( + // CredentialActive represents active credential. + CredentialActive CredentialStatus = "ACTIVE" + // CredentialInactive represents inactive credential. + CredentialInactive CredentialStatus = "INACTIVE" + // CredentialDeleted represents deleted credential. + CredentialDeleted CredentialStatus = "DELETED" +) + +// Validate validates CredentialStatus. +func (c CredentialStatus) Validate() error { + return validation.In(CredentialActive, CredentialInactive, CredentialDeleted). + Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s' or '%s'", + c, CredentialActive, CredentialInactive, CredentialDeleted)). + Validate(c) +} + +// Validate validates GetCredentialRequest. +func (r GetCredentialRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CredentialID": validation.Validate(r.CredentialID, validation.Required), + }) +} + +// Validate validates UpdateCredentialRequest. +func (r UpdateCredentialRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CredentialID": validation.Validate(r.CredentialID, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), + }) +} + +// Validate validates UpdateCredentialRequestBody. +func (r UpdateCredentialRequestBody) Validate() error { + return validation.Errors{ + "ExpiresOn": validation.Validate(r.ExpiresOn, validation.Required), + "Status": validation.Validate(r.Status, validation.Required), + }.Filter() +} + +// Validate validates DeleteCredentialRequest. +func (r DeleteCredentialRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CredentialID": validation.Validate(r.CredentialID, validation.Required), + }) +} + +// Validate validates DeactivateCredentialRequest. +func (r DeactivateCredentialRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CredentialID": validation.Validate(r.CredentialID, validation.Required), + }) +} + +var ( + // ErrCreateCredential is returned when CreateCredential fails. + ErrCreateCredential = errors.New("create credential") + // ErrListCredentials is returned when ListCredentials fails. + ErrListCredentials = errors.New("list credentials") + // ErrGetCredential is returned when GetCredential fails. + ErrGetCredential = errors.New("get credential") + // ErrUpdateCredential is returned when UpdateCredential fails. + ErrUpdateCredential = errors.New("update credential") + // ErrDeleteCredential is returned when DeleteCredential fails. + ErrDeleteCredential = errors.New("delete credential") + // ErrDeactivateCredential is returned when DeactivateCredential fails. + ErrDeactivateCredential = errors.New("deactivate credential") + // ErrDeactivateCredentials is returned when DeactivateCredentials fails. + ErrDeactivateCredentials = errors.New("deactivate credentials") +) + +func (i *iam) CreateCredential(ctx context.Context, params CreateCredentialRequest) (*CreateCredentialResponse, error) { + logger := i.Log(ctx) + logger.Debug("CreateCredential") + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials", params.ClientID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCreateCredential, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateCredential, err) + } + + var result CreateCredentialResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrCreateCredential, err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("%s: %w", ErrCreateCredential, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) ListCredentials(ctx context.Context, params ListCredentialsRequest) (ListCredentialsResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListCredentials") + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials", params.ClientID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListCredentials, err) + } + + q := uri.Query() + q.Add("actions", strconv.FormatBool(params.Actions)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListCredentials, err) + } + + var result ListCredentialsResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListCredentials, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListCredentials, i.Error(resp)) + } + + return result, nil +} + +func (i *iam) GetCredential(ctx context.Context, params GetCredentialRequest) (*GetCredentialResponse, error) { + logger := i.Log(ctx) + logger.Debug("GetCredential") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetCredential, ErrStructValidation, err) + } + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials/%d", params.ClientID, params.CredentialID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrGetCredential, err) + } + + q := uri.Query() + q.Add("actions", strconv.FormatBool(params.Actions)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetCredential, err) + } + + var result GetCredentialResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrGetCredential, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrGetCredential, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) UpdateCredential(ctx context.Context, params UpdateCredentialRequest) (*UpdateCredentialResponse, error) { + logger := i.Log(ctx) + logger.Debug("UpdateCredential") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateCredential, ErrStructValidation, err) + } + + if params.ClientID == "" { + params.ClientID = "self" + } + + // Because API does not accept date without providing milliseconds, if there are no millisecond add a small duration to allow the request to + // be processed. Only applicable when no milliseconds are provided, or they are equal to zero. Ticket for tracking: IDM-3347. + if params.Body.ExpiresOn.Nanosecond() == 0 { + params.Body.ExpiresOn = params.Body.ExpiresOn.Add(time.Nanosecond) + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials/%d", params.ClientID, params.CredentialID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrUpdateCredential, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateCredential, err) + } + + var result UpdateCredentialResponse + resp, err := i.Exec(req, &result, params.Body) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateCredential, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrUpdateCredential, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) DeleteCredential(ctx context.Context, params DeleteCredentialRequest) error { + logger := i.Log(ctx) + logger.Debug("DeleteCredential") + + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrDeleteCredential, ErrStructValidation, err) + } + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials/%d", params.ClientID, params.CredentialID)) + if err != nil { + return fmt.Errorf("%w: failed to parse url: %s", ErrDeleteCredential, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrDeleteCredential, err) + } + + resp, err := i.Exec(req, nil, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrDeleteCredential, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrDeleteCredential, i.Error(resp)) + } + + return nil +} + +func (i *iam) DeactivateCredential(ctx context.Context, params DeactivateCredentialRequest) error { + logger := i.Log(ctx) + logger.Debug("DeactivateCredential") + + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrDeactivateCredential, ErrStructValidation, err) + } + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials/%d/deactivate", params.ClientID, params.CredentialID)) + if err != nil { + return fmt.Errorf("%w: failed to parse url: %s", ErrDeactivateCredential, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrDeactivateCredential, err) + } + + resp, err := i.Exec(req, nil, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrDeactivateCredential, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrDeactivateCredential, i.Error(resp)) + } + + return nil +} + +func (i *iam) DeactivateCredentials(ctx context.Context, params DeactivateCredentialsRequest) error { + logger := i.Log(ctx) + logger.Debug("DeactivateCredentials") + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/credentials/deactivate", params.ClientID)) + if err != nil { + return fmt.Errorf("%w: failed to parse url: %s", ErrDeactivateCredentials, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrDeactivateCredentials, err) + } + + resp, err := i.Exec(req, nil, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrDeactivateCredentials, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrDeactivateCredentials, i.Error(resp)) + } + + return nil +} diff --git a/pkg/iam/api_clients_credentials_test.go b/pkg/iam/api_clients_credentials_test.go new file mode 100644 index 00000000..6eb771ab --- /dev/null +++ b/pkg/iam/api_clients_credentials_test.go @@ -0,0 +1,998 @@ +package iam + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" + "github.com/stretchr/testify/require" + "github.com/tj/assert" +) + +func TestIAM_CreateCredential(t *testing.T) { + tests := map[string]struct { + params CreateCredentialRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse *CreateCredentialResponse + withError func(*testing.T, error) + }{ + "201 Created with specified client": { + params: CreateCredentialRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials", + responseStatus: http.StatusCreated, + responseBody: ` +{ + "credentialId": 123, + "clientToken": "test-token", + "clientSecret": "test-secret", + "createdOn": "2024-07-25T11:02:28.000Z", + "expiresOn": "2026-07-25T11:02:28.000Z", + "status": "ACTIVE", + "description": "" +} +`, + expectedResponse: &CreateCredentialResponse{ + ClientSecret: "test-secret", + ClientToken: "test-token", + CreatedOn: test.NewTimeFromString(t, "2024-07-25T11:02:28.000Z"), + CredentialID: 123, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-07-25T11:02:28.000Z"), + Status: CredentialActive, + }, + }, + "200 OK - self": { + params: CreateCredentialRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/credentials", + responseStatus: http.StatusCreated, + responseBody: ` +{ + "credentialId": 123, + "clientToken": "test-token", + "clientSecret": "test-secret", + "createdOn": "2024-07-25T11:02:28.000Z", + "expiresOn": "2026-07-25T11:02:28.000Z", + "status": "ACTIVE", + "description": "" +} +`, + expectedResponse: &CreateCredentialResponse{ + ClientSecret: "test-secret", + ClientToken: "test-token", + CreatedOn: test.NewTimeFromString(t, "2024-07-25T11:02:28.000Z"), + CredentialID: 123, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-07-25T11:02:28.000Z"), + Status: CredentialActive, + }, + }, + "404 Not Found": { + params: CreateCredentialRequest{ + ClientID: "test12344", + }, + expectedPath: "/identity-management/v3/api-clients/test12344/credentials", + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/identity-management/error-types/2", + "status": 404, + "title": "invalid open identity", + "detail": "", + "instance": "", + "errors": [] +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "invalid open identity", + Type: "/identity-management/error-types/2", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: CreateCredentialRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/credentials", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.CreateCredential(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_ListCredentials(t *testing.T) { + tests := map[string]struct { + params ListCredentialsRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse ListCredentialsResponse + withError func(*testing.T, error) + }{ + "200 OK with specified client": { + params: ListCredentialsRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials?actions=false", + responseStatus: http.StatusOK, + responseBody: ` +[ + { + "credentialId": 1, + "clientToken": "test-token1", + "status": "ACTIVE", + "createdOn": "2024-05-14T11:10:25.000Z", + "description": "", + "expiresOn": "2026-05-14T11:10:25.000Z", + "maxAllowedExpiry": "2026-07-25T11:09:30.658Z" + }, + { + "credentialId": 2, + "clientToken": "test-token2", + "status": "DELETED", + "createdOn": "2024-05-28T06:53:36.000Z", + "description": "deactivate for deletion", + "expiresOn": "2025-10-11T23:06:59.000Z", + "maxAllowedExpiry": "2026-07-25T11:09:30.658Z" + }, + { + "credentialId": 3, + "clientToken": "test-token3", + "status": "ACTIVE", + "createdOn": "2024-07-25T11:02:28.000Z", + "description": "", + "expiresOn": "2026-07-25T11:02:28.000Z", + "maxAllowedExpiry": "2026-07-25T11:09:30.658Z" + } +] +`, + expectedResponse: ListCredentialsResponse{ + { + ClientToken: "test-token1", + CreatedOn: test.NewTimeFromString(t, "2024-05-14T11:10:25.000Z"), + CredentialID: 1, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + MaxAllowedExpiry: test.NewTimeFromString(t, "2026-07-25T11:09:30.658Z"), + }, + { + ClientToken: "test-token2", + CreatedOn: test.NewTimeFromString(t, "2024-05-28T06:53:36.000Z"), + CredentialID: 2, + Description: "deactivate for deletion", + ExpiresOn: test.NewTimeFromString(t, "2025-10-11T23:06:59.000Z"), + Status: CredentialDeleted, + MaxAllowedExpiry: test.NewTimeFromString(t, "2026-07-25T11:09:30.658Z"), + }, + { + ClientToken: "test-token3", + CreatedOn: test.NewTimeFromString(t, "2024-07-25T11:02:28.000Z"), + CredentialID: 3, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-07-25T11:02:28.000Z"), + Status: CredentialActive, + MaxAllowedExpiry: test.NewTimeFromString(t, "2026-07-25T11:09:30.658Z"), + }, + }, + }, + "200 OK - self and actions query param": { + params: ListCredentialsRequest{ + Actions: true, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials?actions=true", + responseStatus: http.StatusOK, + responseBody: ` +[ + { + "credentialId": 1, + "clientToken": "test-token1", + "status": "ACTIVE", + "createdOn": "2024-05-14T11:10:25.000Z", + "description": "", + "expiresOn": "2026-05-14T11:10:25.000Z", + "maxAllowedExpiry": "2026-07-25T11:09:30.658Z", + "actions": { + "deactivate": true, + "delete": true, + "activate": true, + "editDescription": true, + "editExpiration": true + } + } +] +`, + expectedResponse: ListCredentialsResponse{ + { + ClientToken: "test-token1", + CreatedOn: test.NewTimeFromString(t, "2024-05-14T11:10:25.000Z"), + CredentialID: 1, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + MaxAllowedExpiry: test.NewTimeFromString(t, "2026-07-25T11:09:30.658Z"), + Actions: &CredentialActions{ + Deactivate: true, + Delete: true, + Activate: true, + EditDescription: true, + EditExpiration: true, + }, + }, + }, + }, + "404 Not Found": { + params: ListCredentialsRequest{ + ClientID: "test12344", + }, + expectedPath: "/identity-management/v3/api-clients/test12344/credentials?actions=false", + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/identity-management/error-types/2", + "status": 404, + "title": "invalid open identity", + "detail": "", + "instance": "", + "errors": [] +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "invalid open identity", + Type: "/identity-management/error-types/2", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: ListCredentialsRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/credentials?actions=false", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.ListCredentials(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_GetCredential(t *testing.T) { + tests := map[string]struct { + params GetCredentialRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse *GetCredentialResponse + withError func(*testing.T, error) + }{ + "200 OK with specified client": { + params: GetCredentialRequest{ + ClientID: "test1234", + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/123?actions=false", + responseStatus: http.StatusOK, + responseBody: ` +{ + "credentialId": 1, + "clientToken": "test-token1", + "status": "ACTIVE", + "createdOn": "2024-05-14T11:10:25.000Z", + "description": "", + "expiresOn": "2026-05-14T11:10:25.000Z", + "maxAllowedExpiry": "2026-07-25T11:09:30.658Z" +} +`, + expectedResponse: &GetCredentialResponse{ + ClientToken: "test-token1", + CreatedOn: test.NewTimeFromString(t, "2024-05-14T11:10:25.000Z"), + CredentialID: 1, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + MaxAllowedExpiry: test.NewTimeFromString(t, "2026-07-25T11:09:30.658Z"), + }, + }, + "200 OK - self with actions query param": { + params: GetCredentialRequest{ + CredentialID: 123, + Actions: true, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123?actions=true", + responseStatus: http.StatusOK, + responseBody: ` +{ + "credentialId": 1, + "clientToken": "test-token1", + "status": "ACTIVE", + "createdOn": "2024-05-14T11:10:25.000Z", + "description": "", + "expiresOn": "2026-05-14T11:10:25.000Z", + "maxAllowedExpiry": "2026-07-25T11:09:30.658Z", + "actions": { + "deactivate": true, + "delete": true, + "activate": true, + "editDescription": true, + "editExpiration": false + } +} +`, + expectedResponse: &GetCredentialResponse{ + ClientToken: "test-token1", + CreatedOn: test.NewTimeFromString(t, "2024-05-14T11:10:25.000Z"), + CredentialID: 1, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + MaxAllowedExpiry: test.NewTimeFromString(t, "2026-07-25T11:09:30.658Z"), + Actions: &CredentialActions{ + Deactivate: true, + Delete: true, + Activate: true, + EditDescription: true, + EditExpiration: false, + }, + }, + }, + "validation errors": { + params: GetCredentialRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "get credential: struct validation: CredentialID: cannot be blank", err.Error()) + }, + }, + "404 Not Found": { + params: GetCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123?actions=false", + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/identity-management/error-types/25", + "status": 404, + "title": "ERROR_NO_CREDENTIAL", + "detail": "", + "instance": "", + "errors": [] +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "ERROR_NO_CREDENTIAL", + Type: "/identity-management/error-types/25", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: GetCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123?actions=false", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.GetCredential(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_UpdateCredential(t *testing.T) { + tests := map[string]struct { + params UpdateCredentialRequest + responseStatus int + responseBody string + expectedPath string + expectedRequestBody string + expectedResponse *UpdateCredentialResponse + withError func(*testing.T, error) + }{ + "200 OK with zeros as milliseconds - add nanosecond to the request": { + params: UpdateCredentialRequest{ + ClientID: "test1234", + CredentialID: 123, + Body: UpdateCredentialRequestBody{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + }, + }, + expectedRequestBody: ` +{ + "expiresOn": "2026-05-14T11:10:25.000000001Z", + "status": "ACTIVE" +} +`, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/123", + responseStatus: http.StatusOK, + responseBody: ` +{ + "status": "ACTIVE", + "expiresOn": "2026-05-14T11:10:25.000Z" +} +`, + expectedResponse: &UpdateCredentialResponse{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + }, + }, + "200 OK with no milliseconds provided - add nanosecond to the request": { + params: UpdateCredentialRequest{ + ClientID: "test1234", + CredentialID: 123, + Body: UpdateCredentialRequestBody{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25Z"), + Status: CredentialActive, + }, + }, + expectedRequestBody: ` +{ + "expiresOn": "2026-05-14T11:10:25.000000001Z", + "status": "ACTIVE" +} +`, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/123", + responseStatus: http.StatusOK, + responseBody: ` +{ + "status": "ACTIVE", + "expiresOn": "2026-05-14T11:10:25.000Z" +} +`, + expectedResponse: &UpdateCredentialResponse{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + }, + }, + "200 OK with specified client, without description": { + params: UpdateCredentialRequest{ + ClientID: "test1234", + CredentialID: 123, + Body: UpdateCredentialRequestBody{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.123Z"), + Status: CredentialActive, + }, + }, + expectedRequestBody: ` +{ + "expiresOn": "2026-05-14T11:10:25.123Z", + "status": "ACTIVE" +} +`, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/123", + responseStatus: http.StatusOK, + responseBody: ` +{ + "status": "ACTIVE", + "expiresOn": "2026-05-14T11:10:25.000Z" +} +`, + expectedResponse: &UpdateCredentialResponse{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + }, + }, + "200 OK without specified client, with description": { + params: UpdateCredentialRequest{ + CredentialID: 123, + Body: UpdateCredentialRequestBody{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.123Z"), + Status: CredentialInactive, + Description: "test description", + }, + }, + expectedRequestBody: ` +{ + "description": "test description", + "expiresOn": "2026-05-14T11:10:25.123Z", + "status": "INACTIVE" +} +`, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123", + responseStatus: http.StatusOK, + responseBody: ` +{ + "status": "INACTIVE", + "expiresOn": "2026-05-14T11:10:25.000Z", + "description": "test description" +} +`, + expectedResponse: &UpdateCredentialResponse{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialInactive, + Description: ptr.To("test description"), + }, + }, + "validation errors": { + params: UpdateCredentialRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "update credential: struct validation: ExpiresOn: cannot be blank\nStatus: cannot be blank\nCredentialID: cannot be blank", err.Error()) + }, + }, + "404 Not Found": { + params: UpdateCredentialRequest{ + CredentialID: 123, + Body: UpdateCredentialRequestBody{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: "ACTIVE", + }, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123", + responseStatus: http.StatusNotFound, + responseBody: ` + { + "type": "/identity-management/error-types/25", + "status": 404, + "title": "ERROR_NO_CREDENTIAL", + "detail": "", + "instance": "", + "errors": [] + } + `, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "ERROR_NO_CREDENTIAL", + Type: "/identity-management/error-types/25", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: UpdateCredentialRequest{ + CredentialID: 123, + Body: UpdateCredentialRequestBody{ + ExpiresOn: test.NewTimeFromString(t, "2026-05-14T11:10:25.000Z"), + Status: CredentialActive, + }, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + if tc.expectedRequestBody != "" { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.JSONEq(t, tc.expectedRequestBody, string(body)) + } + })) + client := mockAPIClient(t, mockServer) + response, err := client.UpdateCredential(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_DeleteCredential(t *testing.T) { + tests := map[string]struct { + params DeleteCredentialRequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 with specified client": { + params: DeleteCredentialRequest{ + ClientID: "test1234", + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/123", + responseStatus: http.StatusNoContent, + }, + "204 without specified client": { + params: DeleteCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123", + responseStatus: http.StatusNoContent, + }, + "validation errors": { + params: DeleteCredentialRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "delete credential: struct validation: CredentialID: cannot be blank", err.Error()) + }, + }, + "404 Not Found": { + params: DeleteCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123", + responseStatus: http.StatusNotFound, + responseBody: ` + { + "type": "/identity-management/error-types/25", + "status": 404, + "title": "ERROR_NO_CREDENTIAL", + "detail": "", + "instance": "", + "errors": [] + } + `, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "ERROR_NO_CREDENTIAL", + Type: "/identity-management/error-types/25", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: DeleteCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodDelete, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + err := client.DeleteCredential(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_DeactivateCredential(t *testing.T) { + tests := map[string]struct { + params DeactivateCredentialRequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 with specified client": { + params: DeactivateCredentialRequest{ + ClientID: "test1234", + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/123/deactivate", + responseStatus: http.StatusNoContent, + }, + "204 without specified client": { + params: DeactivateCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123/deactivate", + responseStatus: http.StatusNoContent, + }, + "validation errors": { + params: DeactivateCredentialRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "deactivate credential: struct validation: CredentialID: cannot be blank", err.Error()) + }, + }, + "404 Not Found": { + params: DeactivateCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123/deactivate", + responseStatus: http.StatusNotFound, + responseBody: ` + { + "type": "/identity-management/error-types/25", + "status": 404, + "title": "ERROR_NO_CREDENTIAL", + "detail": "", + "instance": "", + "errors": [] + } + `, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "ERROR_NO_CREDENTIAL", + Type: "/identity-management/error-types/25", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: DeactivateCredentialRequest{ + CredentialID: 123, + }, + expectedPath: "/identity-management/v3/api-clients/self/credentials/123/deactivate", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + err := client.DeactivateCredential(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_DeactivateCredentials(t *testing.T) { + tests := map[string]struct { + params DeactivateCredentialsRequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 with specified client": { + params: DeactivateCredentialsRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/credentials/deactivate", + responseStatus: http.StatusNoContent, + }, + "204 without specified client": { + params: DeactivateCredentialsRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/credentials/deactivate", + responseStatus: http.StatusNoContent, + }, + "404 Not Found": { + params: DeactivateCredentialsRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/credentials/deactivate", + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/identity-management/error-types/2", + "status": 404, + "title": "invalid open identity", + "detail": "", + "instance": "", + "errors": [] +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Title: "invalid open identity", + Type: "/identity-management/error-types/2", + StatusCode: http.StatusNotFound, + Errors: json.RawMessage("[]"), + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: DeactivateCredentialsRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/credentials/deactivate", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + err := client.DeactivateCredentials(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/iam/api_clients_test.go b/pkg/iam/api_clients_test.go new file mode 100644 index 00000000..7c321a60 --- /dev/null +++ b/pkg/iam/api_clients_test.go @@ -0,0 +1,2370 @@ +package iam + +import ( + "context" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/stretchr/testify/require" + "github.com/tj/assert" +) + +func TestIAM_LockAPIClient(t *testing.T) { + tests := map[string]struct { + params LockAPIClientRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse *LockAPIClientResponse + withError func(*testing.T, error) + }{ + "200 OK with specified client": { + params: LockAPIClientRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/lock", + responseStatus: http.StatusOK, + responseBody: ` +{ + "accessToken": "test_token1234", + "activeCredentialCount": 1, + "allowAccountSwitch": false, + "authorizedUsers": [ + "jdoe" + ], + "clientDescription": "Test", + "clientId": "abcd1234", + "clientName": "test", + "clientType": "CLIENT", + "createdBy": "jdoe", + "createdDate": "2022-05-13T20:04:35.000Z", + "isLocked": true, + "notificationEmails": [ + "jdoe@example.com" + ], + "serviceConsumerToken": "test_token12345" +}`, + expectedResponse: &LockAPIClientResponse{ + AccessToken: "test_token1234", + ActiveCredentialCount: 1, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"jdoe"}, + CanAutoCreateCredential: false, + ClientDescription: "Test", + ClientID: "abcd1234", + ClientName: "test", + ClientType: "CLIENT", + CreatedBy: "jdoe", + CreatedDate: test.NewTimeFromString(t, "2022-05-13T20:04:35.000Z"), + IsLocked: true, + NotificationEmails: []string{"jdoe@example.com"}, + ServiceConsumerToken: "test_token12345", + }, + }, + "200 OK - self": { + params: LockAPIClientRequest{}, + expectedPath: "/identity-management/v3/api-clients/self/lock", + responseStatus: http.StatusOK, + responseBody: ` +{ + "accessToken": "test_token1234", + "activeCredentialCount": 1, + "allowAccountSwitch": false, + "authorizedUsers": [ + "jdoe" + ], + "clientDescription": "Test", + "clientId": "abcd1234", + "clientName": "test", + "clientType": "CLIENT", + "createdBy": "jdoe", + "createdDate": "2022-05-13T20:04:35.000Z", + "isLocked": true, + "notificationEmails": [ + "jdoe@example.com" + ], + "serviceConsumerToken": "test_token12345" +}`, + expectedResponse: &LockAPIClientResponse{ + AccessToken: "test_token1234", + ActiveCredentialCount: 1, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"jdoe"}, + CanAutoCreateCredential: false, + ClientDescription: "Test", + ClientID: "abcd1234", + ClientName: "test", + ClientType: "CLIENT", + CreatedBy: "jdoe", + CreatedDate: test.NewTimeFromString(t, "2022-05-13T20:04:35.000Z"), + IsLocked: true, + NotificationEmails: []string{"jdoe@example.com"}, + ServiceConsumerToken: "test_token12345", + }, + }, + "404 Not Found": { + params: LockAPIClientRequest{ + ClientID: "test12344", + }, + expectedPath: "/identity-management/v3/api-clients/test12344/lock", + responseStatus: http.StatusNotFound, + responseBody: ` + { + "instance": "", + "httpStatus": 404, + "detail": "", + "title": "invalid open identity", + "type": "/identity-management/error-types/2" + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Instance: "", + HTTPStatus: http.StatusNotFound, + Detail: "", + Title: "invalid open identity", + Type: "/identity-management/error-types/2", + StatusCode: http.StatusNotFound, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: LockAPIClientRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/lock", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.LockAPIClient(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_UnlockAPIClient(t *testing.T) { + tests := map[string]struct { + params UnlockAPIClientRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse *UnlockAPIClientResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: UnlockAPIClientRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/unlock", + responseStatus: http.StatusOK, + responseBody: ` +{ + "accessToken": "test_token1234", + "activeCredentialCount": 1, + "allowAccountSwitch": false, + "authorizedUsers": [ + "jdoe" + ], + "clientDescription": "Test", + "clientId": "abcd1234", + "clientName": "test", + "clientType": "CLIENT", + "createdBy": "jdoe", + "createdDate": "2022-05-13T20:04:35.000Z", + "isLocked": true, + "notificationEmails": [ + "jdoe@example.com" + ], + "serviceConsumerToken": "test_token12345" +}`, + expectedResponse: &UnlockAPIClientResponse{ + AccessToken: "test_token1234", + ActiveCredentialCount: 1, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"jdoe"}, + CanAutoCreateCredential: false, + ClientDescription: "Test", + ClientID: "abcd1234", + ClientName: "test", + ClientType: "CLIENT", + CreatedBy: "jdoe", + CreatedDate: test.NewTimeFromString(t, "2022-05-13T20:04:35.000Z"), + IsLocked: true, + NotificationEmails: []string{"jdoe@example.com"}, + ServiceConsumerToken: "test_token12345", + }, + }, + "validation errors": { + params: UnlockAPIClientRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "unlock api client: struct validation:\nClientID: cannot be blank", err.Error()) + }, + }, + "404 Not Found": { + params: UnlockAPIClientRequest{ + ClientID: "test12344", + }, + expectedPath: "/identity-management/v3/api-clients/test12344/unlock", + responseStatus: http.StatusNotFound, + responseBody: ` + { + "instance": "", + "httpStatus": 404, + "detail": "", + "title": "invalid open identity", + "type": "/identity-management/error-types/2" + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Instance: "", + HTTPStatus: http.StatusNotFound, + Detail: "", + Title: "invalid open identity", + Type: "/identity-management/error-types/2", + StatusCode: http.StatusNotFound, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: UnlockAPIClientRequest{ + ClientID: "test1234", + }, + expectedPath: "/identity-management/v3/api-clients/test1234/unlock", + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.UnlockAPIClient(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_ListAPIClients(t *testing.T) { + tests := map[string]struct { + params ListAPIClientsRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse ListAPIClientsResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: ListAPIClientsRequest{}, + responseStatus: http.StatusOK, + responseBody: ` +[ + { + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "serviceConsumerToken": "akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1" + }, + { + "clientId": "hgfedcba87654321", + "clientName": "test_user_2", + "clientDescription": "test_user_2 description", + "clientType": "SERVICE_ACCOUNT", + "authorizedUsers": [ + "user2" + ], + "canAutoCreateCredential": true, + "notificationEmails": [ + "user2@example.com" + ], + "activeCredentialCount": 1, + "allowAccountSwitch": false, + "createdDate": "2023-07-03T15:04:01.000Z", + "createdBy": "admin", + "isLocked": false, + "accessToken": "akaa-8h7g6f5e8h7g6f5e-8h7g6f5e8h7g6f5e", + "serviceConsumerToken": "akaa-e5f6g7h8e5f6g7h8-e5f6g7h8e5f6g7h8" + } +]`, + expectedPath: "/identity-management/v3/api-clients?actions=false", + expectedResponse: ListAPIClientsResponse{ + { + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + ActiveCredentialCount: 0, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + NotificationEmails: []string{"user1@example.com"}, + ServiceConsumerToken: "akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1", + }, + { + AccessToken: "akaa-8h7g6f5e8h7g6f5e-8h7g6f5e8h7g6f5e", + ActiveCredentialCount: 1, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user2"}, + CanAutoCreateCredential: true, + ClientDescription: "test_user_2 description", + ClientID: "hgfedcba87654321", + ClientName: "test_user_2", + ClientType: ServiceAccountClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2023-07-03T15:04:01.000Z"), + IsLocked: false, + NotificationEmails: []string{"user2@example.com"}, + ServiceConsumerToken: "akaa-e5f6g7h8e5f6g7h8-e5f6g7h8e5f6g7h8", + }, + }, + }, + "200 with actions": { + params: ListAPIClientsRequest{Actions: true}, + responseStatus: http.StatusOK, + responseBody: ` +[ + { + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "serviceConsumerToken": "akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1", + "actions": { + "lock": false, + "unlock": false, + "edit": false, + "transfer": false, + "delete": false, + "deactivateAll": false + } + }, + { + "clientId": "hgfedcba87654321", + "clientName": "test_user_2", + "clientDescription": "test_user_2 description", + "clientType": "SERVICE_ACCOUNT", + "authorizedUsers": [ + "user2" + ], + "canAutoCreateCredential": true, + "notificationEmails": [ + "user2@example.com" + ], + "activeCredentialCount": 1, + "allowAccountSwitch": false, + "createdDate": "2023-07-03T15:04:01.000Z", + "createdBy": "admin", + "isLocked": false, + "accessToken": "akaa-8h7g6f5e8h7g6f5e-8h7g6f5e8h7g6f5e", + "serviceConsumerToken": "akaa-e5f6g7h8e5f6g7h8-e5f6g7h8e5f6g7h8", + "actions": { + "lock": true, + "unlock": true, + "edit": true, + "transfer": true, + "delete": true, + "deactivateAll": true + } + } +]`, + expectedPath: "/identity-management/v3/api-clients?actions=true", + expectedResponse: ListAPIClientsResponse{ + { + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + ActiveCredentialCount: 0, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + NotificationEmails: []string{"user1@example.com"}, + ServiceConsumerToken: "akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1", + Actions: &ListAPIClientsActions{ + Delete: false, + DeactivateAll: false, + Edit: false, + Lock: false, + Transfer: false, + Unlock: false, + }, + }, + { + AccessToken: "akaa-8h7g6f5e8h7g6f5e-8h7g6f5e8h7g6f5e", + ActiveCredentialCount: 1, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user2"}, + CanAutoCreateCredential: true, + ClientDescription: "test_user_2 description", + ClientID: "hgfedcba87654321", + ClientName: "test_user_2", + ClientType: ServiceAccountClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2023-07-03T15:04:01.000Z"), + IsLocked: false, + NotificationEmails: []string{"user2@example.com"}, + ServiceConsumerToken: "akaa-e5f6g7h8e5f6g7h8-e5f6g7h8e5f6g7h8", + Actions: &ListAPIClientsActions{ + Delete: true, + DeactivateAll: true, + Edit: true, + Lock: true, + Transfer: true, + Unlock: true, + }, + }, + }, + }, + "500 internal server error": { + params: ListAPIClientsRequest{}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/api-clients?actions=false", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + result, err := client.ListAPIClients(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_CreateAPIClient(t *testing.T) { + tests := map[string]struct { + params CreateAPIClientRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse *CreateAPIClientResponse + expectedRequestBody string + withError func(*testing.T, error) + }{ + "201 Created with allAPI, cpCodes and clone group": { + params: CreateAPIClientRequest{ + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + }, + AuthorizedUsers: []string{"user1"}, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + }, + NotificationEmails: []string{"user1@example.com"}, + PurgeOptions: &PurgeOptions{ + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + }, + }, + }, + expectedPath: "/identity-management/v3/api-clients", + responseStatus: http.StatusCreated, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "groupAccess": { + "cloneAuthorizedUserGroups": true, + "groups": [ + { + "groupId": 123, + "groupName": "GroupName-G-R0UP", + "roleId": 1, + "roleName": "Admin", + "roleDescription": "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + "isBlocked": false, + "subGroups": [] + } + ] + }, + "apiAccess": { + "allAccessibleApis": true, + "apis": [ + { + "apiId": 1, + "apiName": "API Client Administration", + "description": "API Client Administration", + "endPoint": "/identity-management", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-ONLY" + }, + { + "apiId": 2, + "apiName": "CCU APIs", + "description": "Content control utility APIs", + "endPoint": "/ccu", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-WRITE" + } + ] + }, + "purgeOptions": { + "canPurgeByCpcode": false, + "canPurgeByCacheTag": false, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": true, + "cpcodes": [] + } + }, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "actions": { + "editGroups": true, + "editApis": true, + "lock": true, + "unlock": false, + "editAuth": true, + "edit": true, + "editSwitchAccount": false, + "transfer": true, + "editIpAcl": true, + "delete": true, + "deactivateAll": false + } +}`, + expectedRequestBody: ` +{ + "allowAccountSwitch": false, + "apiAccess": { + "allAccessibleApis": true, + "apis": null + }, + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "clientDescription": "test_user_1 description", + "clientName": "test_user_1", + "clientType": "CLIENT", + "createCredential": false, + "groupAccess": { + "cloneAuthorizedUserGroups": true, + "groups": null + }, + "notificationEmails": [ + "user1@example.com" + ], + "purgeOptions": { + "canPurgeByCacheTag": false, + "canPurgeByCpcode": false, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": true, + "cpcodes": null + } + } +} +`, + expectedResponse: &CreateAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ActiveCredentialCount: 0, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + Groups: []ClientGroup{ + { + GroupID: 123, + GroupName: "GroupName-G-R0UP", + RoleID: 1, + RoleName: "Admin", + RoleDescription: "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + Subgroups: []ClientGroup{}, + }, + }, + }, + NotificationEmails: []string{"user1@example.com"}, + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + APIs: []API{ + { + APIID: 1, + APIName: "API Client Administration", + Description: "API Client Administration", + Endpoint: "/identity-management", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadOnlyLevel, + }, + { + APIID: 2, + APIName: "CCU APIs", + Description: "Content control utility APIs", + Endpoint: "/ccu", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadWriteLevel, + }, + }, + }, + PurgeOptions: PurgeOptions{ + CanPurgeByCPCode: false, + CanPurgeByCacheTag: false, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + CPCodes: []int64{}, + }, + }, + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + Actions: &APIClientActions{ + EditGroups: true, + EditAPIs: true, + Lock: true, + Unlock: false, + EditAuth: true, + Edit: true, + EditSwitchAccount: false, + Transfer: true, + EditIPAcl: true, + Delete: true, + DeactivateAll: false, + }, + }, + }, + "201 Created with all fields and custom API and group": { + params: CreateAPIClientRequest{ + AllowAccountSwitch: true, + APIAccess: APIAccess{ + AllAccessibleAPIs: false, + APIs: []API{ + { + AccessLevel: ReadOnlyLevel, + APIID: 1, + }, + { + AccessLevel: ReadWriteLevel, + APIID: 2, + }, + }, + }, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: true, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreateCredential: true, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: false, + Groups: []ClientGroup{ + { + GroupID: 123, + RoleID: 1, + }, + }, + }, + IPACL: &IPACL{ + CIDR: []string{"1.2.3.4/32"}, + Enable: true, + }, + NotificationEmails: []string{"user1@example.com"}, + PurgeOptions: &PurgeOptions{ + CanPurgeByCacheTag: true, + CanPurgeByCPCode: true, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: false, + CPCodes: []int64{321}, + }, + }, + }, + expectedPath: "/identity-management/v3/api-clients", + responseStatus: http.StatusCreated, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": true, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 1, + "allowAccountSwitch": true, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "groupAccess": { + "cloneAuthorizedUserGroups": false, + "groups": [ + { + "groupId": 123, + "groupName": "GroupName-G-R0UP", + "roleId": 1, + "roleName": "Admin", + "roleDescription": "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + "isBlocked": false, + "subGroups": [] + } + ] + }, + "apiAccess": { + "allAccessibleApis": false, + "apis": [ + { + "apiId": 1, + "apiName": "API Client Administration", + "description": "API Client Administration", + "endPoint": "/identity-management", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-ONLY" + }, + { + "apiId": 2, + "apiName": "CCU APIs", + "description": "Content control utility APIs", + "endPoint": "/ccu", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-WRITE" + } + ] + }, + "purgeOptions": { + "canPurgeByCpcode": true, + "canPurgeByCacheTag": true, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": false, + "cpcodes": [321] + } + }, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "credentials": [ + { + "credentialId": 456, + "clientToken": "akaa-bc78bc78bc78bc78-bc78bc78bc78bc78", + "clientSecret": "verysecretsecret", + "status": "ACTIVE", + "createdOn": "2023-01-03T07:44:08.000Z", + "description": "desc", + "expiresOn": "2025-01-03T07:44:08.000Z", + "actions": { + "deactivate": true, + "delete": false, + "activate": false, + "editDescription": true, + "editExpiration": true + } + } + ], + "actions": { + "editGroups": true, + "editApis": true, + "lock": true, + "unlock": false, + "editAuth": true, + "edit": true, + "editSwitchAccount": false, + "transfer": true, + "editIpAcl": true, + "delete": true, + "deactivateAll": false + } +}`, + expectedRequestBody: ` +{ + "allowAccountSwitch": true, + "apiAccess": { + "allAccessibleApis": false, + "apis": [ + { + "accessLevel": "READ-ONLY", + "apiId": 1, + "apiName": "", + "description": "", + "documentationUrl": "", + "endPoint": "" + }, + { + "accessLevel": "READ-WRITE", + "apiId": 2, + "apiName": "", + "description": "", + "documentationUrl": "", + "endPoint": "" + } + ] + }, + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": true, + "clientDescription": "test_user_1 description", + "clientName": "test_user_1", + "clientType": "CLIENT", + "createCredential": true, + "groupAccess": { + "cloneAuthorizedUserGroups": false, + "groups": [ + { + "groupId": 123, + "groupName": "", + "isBlocked": false, + "parentGroupId": 0, + "roleDescription": "", + "roleId": 1, + "roleName": "", + "subgroups": null + } + ] + }, + "ipAcl": { + "cidr": [ + "1.2.3.4/32" + ], + "enable": true + }, + "notificationEmails": [ + "user1@example.com" + ], + "purgeOptions": { + "canPurgeByCacheTag": true, + "canPurgeByCpcode": true, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": false, + "cpcodes": [ + 321 + ] + } + } +} +`, + expectedResponse: &CreateAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + AllowAccountSwitch: true, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: true, + ActiveCredentialCount: 1, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: false, + Groups: []ClientGroup{ + { + GroupID: 123, + GroupName: "GroupName-G-R0UP", + RoleID: 1, + RoleName: "Admin", + RoleDescription: "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + Subgroups: []ClientGroup{}, + }, + }, + }, + NotificationEmails: []string{"user1@example.com"}, + APIAccess: APIAccess{ + AllAccessibleAPIs: false, + APIs: []API{ + { + APIID: 1, + APIName: "API Client Administration", + Description: "API Client Administration", + Endpoint: "/identity-management", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadOnlyLevel, + }, + { + APIID: 2, + APIName: "CCU APIs", + Description: "Content control utility APIs", + Endpoint: "/ccu", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadWriteLevel, + }, + }, + }, + PurgeOptions: PurgeOptions{ + CanPurgeByCacheTag: true, + CanPurgeByCPCode: true, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: false, + CPCodes: []int64{321}, + }, + }, + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + Credentials: []CreateAPIClientCredential{ + { + Actions: CredentialActions{ + Deactivate: true, + Delete: false, + Activate: false, + EditDescription: true, + EditExpiration: true, + }, + ClientToken: "akaa-bc78bc78bc78bc78-bc78bc78bc78bc78", + ClientSecret: "verysecretsecret", + CreatedOn: test.NewTimeFromString(t, "2023-01-03T07:44:08.000Z"), + CredentialID: 456, + Description: "desc", + ExpiresOn: test.NewTimeFromString(t, "2025-01-03T07:44:08.000Z"), + Status: CredentialActive, + }, + }, + Actions: &APIClientActions{ + EditGroups: true, + EditAPIs: true, + Lock: true, + Unlock: false, + EditAuth: true, + Edit: true, + EditSwitchAccount: false, + Transfer: true, + EditIPAcl: true, + Delete: true, + DeactivateAll: false, + }, + }, + }, + "validation errors": { + params: CreateAPIClientRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "create api client: struct validation:\nAPIs: cannot be blank\nAuthorizedUsers: cannot be blank\nClientType: cannot be blank\nGroups: cannot be blank", err.Error()) + }, + }, + "validation errors - internal validations": { + params: CreateAPIClientRequest{APIAccess: APIAccess{APIs: []API{{}}}, AuthorizedUsers: []string{"user1"}, ClientType: "abc", GroupAccess: GroupAccess{Groups: []ClientGroup{{}}}, PurgeOptions: &PurgeOptions{CPCodeAccess: CPCodeAccess{AllCurrentAndNewCPCodes: false, CPCodes: nil}}}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "create api client: struct validation:\nAPIs[0]: {\n\tAPIID: cannot be blank\n\tAccessLevel: cannot be blank\n}\nClientType: value 'abc' is invalid. Must be one of: 'CLIENT', 'SERVICE_ACCOUNT' or 'USER_CLIENT'\nGroups[0]: {\n\tGroupID: cannot be blank\n\tRoleID: cannot be blank\n}\nCPCodes: is required", err.Error()) + }, + }, + "500 internal server error": { + params: CreateAPIClientRequest{ + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + }, + AuthorizedUsers: []string{"user1"}, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + }, + NotificationEmails: []string{"user1@example.com"}, + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +} +`, + expectedPath: "/identity-management/v3/api-clients", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, tc.expectedRequestBody, string(body)) + } + + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.CreateAPIClient(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_UpdateAPIClient(t *testing.T) { + tests := map[string]struct { + params UpdateAPIClientRequest + expectedPath string + responseStatus int + responseBody string + expectedResponse *UpdateAPIClientResponse + expectedRequestBody string + withError func(*testing.T, error) + }{ + "200 Updated self": { + params: UpdateAPIClientRequest{ + Body: UpdateAPIClientRequestBody{ + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + }, + AuthorizedUsers: []string{"user1"}, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + }, + NotificationEmails: []string{"user1@example.com"}, + PurgeOptions: &PurgeOptions{ + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + }, + }, + }, + }, + expectedPath: "/identity-management/v3/api-clients/self", + responseStatus: http.StatusOK, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "groupAccess": { + "cloneAuthorizedUserGroups": true, + "groups": [ + { + "groupId": 123, + "groupName": "GroupName-G-R0UP", + "roleId": 1, + "roleName": "Admin", + "roleDescription": "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + "isBlocked": false, + "subGroups": [] + } + ] + }, + "apiAccess": { + "allAccessibleApis": true, + "apis": [ + { + "apiId": 1, + "apiName": "API Client Administration", + "description": "API Client Administration", + "endPoint": "/identity-management", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-ONLY" + }, + { + "apiId": 2, + "apiName": "CCU APIs", + "description": "Content control utility APIs", + "endPoint": "/ccu", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-WRITE" + } + ] + }, + "purgeOptions": { + "canPurgeByCpcode": false, + "canPurgeByCacheTag": false, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": true, + "cpcodes": [] + } + }, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "actions": { + "editGroups": true, + "editApis": true, + "lock": true, + "unlock": false, + "editAuth": true, + "edit": true, + "editSwitchAccount": false, + "transfer": true, + "editIpAcl": true, + "delete": true, + "deactivateAll": false + } +}`, + expectedResponse: &UpdateAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ActiveCredentialCount: 0, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + Groups: []ClientGroup{ + { + GroupID: 123, + GroupName: "GroupName-G-R0UP", + RoleID: 1, + RoleName: "Admin", + RoleDescription: "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + Subgroups: []ClientGroup{}, + }, + }, + }, + NotificationEmails: []string{"user1@example.com"}, + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + APIs: []API{ + { + APIID: 1, + APIName: "API Client Administration", + Description: "API Client Administration", + Endpoint: "/identity-management", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadOnlyLevel, + }, + { + APIID: 2, + APIName: "CCU APIs", + Description: "Content control utility APIs", + Endpoint: "/ccu", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadWriteLevel, + }, + }, + }, + PurgeOptions: PurgeOptions{ + CanPurgeByCPCode: false, + CanPurgeByCacheTag: false, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + CPCodes: []int64{}, + }, + }, + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + Actions: &APIClientActions{ + EditGroups: true, + EditAPIs: true, + Lock: true, + Unlock: false, + EditAuth: true, + Edit: true, + EditSwitchAccount: false, + Transfer: true, + EditIPAcl: true, + Delete: true, + DeactivateAll: false, + }, + }, + }, + "200 Updated with allAPI, cpCodes and clone group": { + params: UpdateAPIClientRequest{ + ClientID: "abcdefgh12345678", + Body: UpdateAPIClientRequestBody{ + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + }, + AuthorizedUsers: []string{"user1"}, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + }, + NotificationEmails: []string{"user1@example.com"}, + PurgeOptions: &PurgeOptions{ + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + }, + }, + }, + }, + expectedPath: "/identity-management/v3/api-clients/abcdefgh12345678", + responseStatus: http.StatusOK, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "groupAccess": { + "cloneAuthorizedUserGroups": true, + "groups": [ + { + "groupId": 123, + "groupName": "GroupName-G-R0UP", + "roleId": 1, + "roleName": "Admin", + "roleDescription": "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + "isBlocked": false, + "subGroups": [] + } + ] + }, + "apiAccess": { + "allAccessibleApis": true, + "apis": [ + { + "apiId": 1, + "apiName": "API Client Administration", + "description": "API Client Administration", + "endPoint": "/identity-management", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-ONLY" + }, + { + "apiId": 2, + "apiName": "CCU APIs", + "description": "Content control utility APIs", + "endPoint": "/ccu", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-WRITE" + } + ] + }, + "purgeOptions": { + "canPurgeByCpcode": false, + "canPurgeByCacheTag": false, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": true, + "cpcodes": [] + } + }, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "actions": { + "editGroups": true, + "editApis": true, + "lock": true, + "unlock": false, + "editAuth": true, + "edit": true, + "editSwitchAccount": false, + "transfer": true, + "editIpAcl": true, + "delete": true, + "deactivateAll": false + } +}`, + expectedRequestBody: ` +{ + "allowAccountSwitch": false, + "apiAccess": { + "allAccessibleApis": true, + "apis": null + }, + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "clientDescription": "test_user_1 description", + "clientName": "test_user_1", + "clientType": "CLIENT", + "groupAccess": { + "cloneAuthorizedUserGroups": true, + "groups": null + }, + "notificationEmails": [ + "user1@example.com" + ], + "purgeOptions": { + "canPurgeByCacheTag": false, + "canPurgeByCpcode": false, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": true, + "cpcodes": null + } + } +} +`, + expectedResponse: &UpdateAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ActiveCredentialCount: 0, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + Groups: []ClientGroup{ + { + GroupID: 123, + GroupName: "GroupName-G-R0UP", + RoleID: 1, + RoleName: "Admin", + RoleDescription: "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + Subgroups: []ClientGroup{}, + }, + }, + }, + NotificationEmails: []string{"user1@example.com"}, + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + APIs: []API{ + { + APIID: 1, + APIName: "API Client Administration", + Description: "API Client Administration", + Endpoint: "/identity-management", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadOnlyLevel, + }, + { + APIID: 2, + APIName: "CCU APIs", + Description: "Content control utility APIs", + Endpoint: "/ccu", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadWriteLevel, + }, + }, + }, + PurgeOptions: PurgeOptions{ + CanPurgeByCPCode: false, + CanPurgeByCacheTag: false, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + CPCodes: []int64{}, + }, + }, + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + Actions: &APIClientActions{ + EditGroups: true, + EditAPIs: true, + Lock: true, + Unlock: false, + EditAuth: true, + Edit: true, + EditSwitchAccount: false, + Transfer: true, + EditIPAcl: true, + Delete: true, + DeactivateAll: false, + }, + }, + }, + "200 Updated with all fields and custom API and group": { + params: UpdateAPIClientRequest{ + ClientID: "abcdefgh12345678", + Body: UpdateAPIClientRequestBody{ + AllowAccountSwitch: true, + APIAccess: APIAccess{ + AllAccessibleAPIs: false, + APIs: []API{ + { + AccessLevel: ReadOnlyLevel, + APIID: 1, + }, + { + AccessLevel: ReadWriteLevel, + APIID: 2, + }, + }, + }, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: true, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: false, + Groups: []ClientGroup{ + { + GroupID: 123, + RoleID: 1, + }, + }, + }, + IPACL: &IPACL{ + CIDR: []string{"1.2.3.4/32"}, + Enable: true, + }, + NotificationEmails: []string{"user1@example.com"}, + PurgeOptions: &PurgeOptions{ + CanPurgeByCacheTag: true, + CanPurgeByCPCode: true, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: false, + CPCodes: []int64{321}, + }, + }, + }, + }, + expectedPath: "/identity-management/v3/api-clients/abcdefgh12345678", + responseStatus: http.StatusOK, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": true, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 1, + "allowAccountSwitch": true, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "groupAccess": { + "cloneAuthorizedUserGroups": false, + "groups": [ + { + "groupId": 123, + "groupName": "GroupName-G-R0UP", + "roleId": 1, + "roleName": "Admin", + "roleDescription": "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + "isBlocked": false, + "subGroups": [] + } + ] + }, + "apiAccess": { + "allAccessibleApis": false, + "apis": [ + { + "apiId": 1, + "apiName": "API Client Administration", + "description": "API Client Administration", + "endPoint": "/identity-management", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-ONLY" + }, + { + "apiId": 2, + "apiName": "CCU APIs", + "description": "Content control utility APIs", + "endPoint": "/ccu", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-WRITE" + } + ] + }, + "purgeOptions": { + "canPurgeByCpcode": true, + "canPurgeByCacheTag": true, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": false, + "cpcodes": [321] + } + }, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "credentials": [ + { + "credentialId": 456, + "clientToken": "akaa-bc78bc78bc78bc78-bc78bc78bc78bc78", + "status": "ACTIVE", + "createdOn": "2023-01-03T07:44:08.000Z", + "description": "desc", + "expiresOn": "2025-01-03T07:44:08.000Z", + "actions": { + "deactivate": true, + "delete": false, + "activate": false, + "editDescription": true, + "editExpiration": true + } + } + ], + "actions": { + "editGroups": true, + "editApis": true, + "lock": true, + "unlock": false, + "editAuth": true, + "edit": true, + "editSwitchAccount": false, + "transfer": true, + "editIpAcl": true, + "delete": true, + "deactivateAll": false + } +}`, + expectedRequestBody: ` +{ + "allowAccountSwitch": true, + "apiAccess": { + "allAccessibleApis": false, + "apis": [ + { + "accessLevel": "READ-ONLY", + "apiId": 1, + "apiName": "", + "description": "", + "documentationUrl": "", + "endPoint": "" + }, + { + "accessLevel": "READ-WRITE", + "apiId": 2, + "apiName": "", + "description": "", + "documentationUrl": "", + "endPoint": "" + } + ] + }, + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": true, + "clientDescription": "test_user_1 description", + "clientName": "test_user_1", + "clientType": "CLIENT", + "groupAccess": { + "cloneAuthorizedUserGroups": false, + "groups": [ + { + "groupId": 123, + "groupName": "", + "isBlocked": false, + "parentGroupId": 0, + "roleDescription": "", + "roleId": 1, + "roleName": "", + "subgroups": null + } + ] + }, + "ipAcl": { + "cidr": [ + "1.2.3.4/32" + ], + "enable": true + }, + "notificationEmails": [ + "user1@example.com" + ], + "purgeOptions": { + "canPurgeByCacheTag": true, + "canPurgeByCpcode": true, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": false, + "cpcodes": [ + 321 + ] + } + } +} +`, + expectedResponse: &UpdateAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + AllowAccountSwitch: true, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: true, + ActiveCredentialCount: 1, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: false, + Groups: []ClientGroup{ + { + GroupID: 123, + GroupName: "GroupName-G-R0UP", + RoleID: 1, + RoleName: "Admin", + RoleDescription: "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + Subgroups: []ClientGroup{}, + }, + }, + }, + NotificationEmails: []string{"user1@example.com"}, + APIAccess: APIAccess{ + AllAccessibleAPIs: false, + APIs: []API{ + { + APIID: 1, + APIName: "API Client Administration", + Description: "API Client Administration", + Endpoint: "/identity-management", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadOnlyLevel, + }, + { + APIID: 2, + APIName: "CCU APIs", + Description: "Content control utility APIs", + Endpoint: "/ccu", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadWriteLevel, + }, + }, + }, + PurgeOptions: PurgeOptions{ + CanPurgeByCacheTag: true, + CanPurgeByCPCode: true, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: false, + CPCodes: []int64{321}, + }, + }, + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + Credentials: []APIClientCredential{ + { + Actions: CredentialActions{ + Deactivate: true, + Delete: false, + Activate: false, + EditDescription: true, + EditExpiration: true, + }, + ClientToken: "akaa-bc78bc78bc78bc78-bc78bc78bc78bc78", + CreatedOn: test.NewTimeFromString(t, "2023-01-03T07:44:08.000Z"), + CredentialID: 456, + Description: "desc", + ExpiresOn: test.NewTimeFromString(t, "2025-01-03T07:44:08.000Z"), + Status: CredentialActive, + }, + }, + Actions: &APIClientActions{ + EditGroups: true, + EditAPIs: true, + Lock: true, + Unlock: false, + EditAuth: true, + Edit: true, + EditSwitchAccount: false, + Transfer: true, + EditIPAcl: true, + Delete: true, + DeactivateAll: false, + }, + }, + }, + "validation errors": { + params: UpdateAPIClientRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "update api client: struct validation:\nAPIs: cannot be blank\nAuthorizedUsers: cannot be blank\nClientName: cannot be blank\nClientType: cannot be blank\nGroups: cannot be blank", err.Error()) + }, + }, + "validation errors - internal validations": { + params: UpdateAPIClientRequest{Body: UpdateAPIClientRequestBody{APIAccess: APIAccess{APIs: []API{{}}}, AuthorizedUsers: []string{"user1"}, ClientType: "abc", GroupAccess: GroupAccess{Groups: []ClientGroup{{}}}, PurgeOptions: &PurgeOptions{CPCodeAccess: CPCodeAccess{AllCurrentAndNewCPCodes: false, CPCodes: nil}}}}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "update api client: struct validation:\nAPIs[0]: {\n\tAPIID: cannot be blank\n\tAccessLevel: cannot be blank\n}\nClientName: cannot be blank\nClientType: value 'abc' is invalid. Must be one of: 'CLIENT', 'SERVICE_ACCOUNT' or 'USER_CLIENT'\nGroups[0]: {\n\tGroupID: cannot be blank\n\tRoleID: cannot be blank\n}\nCPCodes: is required", err.Error()) + }, + }, + "500 internal server error": { + params: UpdateAPIClientRequest{ + Body: UpdateAPIClientRequestBody{ + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + }, + AuthorizedUsers: []string{"user1"}, + ClientDescription: "test_user_1 description", + ClientName: "test_user_1", + ClientType: ClientClientType, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + }, + NotificationEmails: []string{"user1@example.com"}, + }, + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/api-clients/self", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, tc.expectedRequestBody, string(body)) + } + + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + response, err := client.UpdateAPIClient(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, response) + }) + } +} + +func TestIAM_GetAPIClient(t *testing.T) { + tests := map[string]struct { + params GetAPIClientRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetAPIClientResponse + withError func(*testing.T, error) + }{ + "200 OK - Self": { + params: GetAPIClientRequest{}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d" +} +`, + expectedPath: "/identity-management/v3/api-clients/self?actions=false&apiAccess=false&credentials=false&groupAccess=false&ipAcl=false", + expectedResponse: &GetAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + ActiveCredentialCount: 0, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + NotificationEmails: []string{"user1@example.com"}, + }, + }, + "200 OK - with clientID": { + params: GetAPIClientRequest{ClientID: "abcdefgh12345678"}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d" +} +`, + expectedPath: "/identity-management/v3/api-clients/abcdefgh12345678?actions=false&apiAccess=false&credentials=false&groupAccess=false&ipAcl=false", + expectedResponse: &GetAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + ActiveCredentialCount: 0, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + NotificationEmails: []string{"user1@example.com"}, + }, + }, + "200 OK - with all query params and all fields": { + params: GetAPIClientRequest{ + ClientID: "abcdefgh12345678", + Actions: true, + GroupAccess: true, + APIAccess: true, + Credentials: true, + IPACL: true, + }, + responseStatus: http.StatusOK, + responseBody: ` +{ + "clientId": "abcdefgh12345678", + "clientName": "test_user_1", + "clientDescription": "test_user_1 description", + "clientType": "CLIENT", + "authorizedUsers": [ + "user1" + ], + "canAutoCreateCredential": false, + "notificationEmails": [ + "user1@example.com" + ], + "activeCredentialCount": 0, + "allowAccountSwitch": false, + "createdDate": "2024-07-16T23:01:50.000Z", + "createdBy": "admin", + "isLocked": false, + "baseURL": "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + "accessToken": "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + "groupAccess": { + "cloneAuthorizedUserGroups": true, + "groups": [ + { + "groupId": 123, + "groupName": "GroupName-G-R0UP", + "roleId": 1, + "roleName": "Admin", + "roleDescription": "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + "isBlocked": false, + "subGroups": [] + } + ] + }, + "apiAccess": { + "allAccessibleApis": true, + "apis": [ + { + "apiId": 1, + "apiName": "API Client Administration", + "description": "API Client Administration", + "endPoint": "/identity-management", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-ONLY" + }, + { + "apiId": 2, + "apiName": "CCU APIs", + "description": "Content control utility APIs", + "endPoint": "/ccu", + "documentationUrl": "https://developer.akamai.com", + "accessLevel": "READ-WRITE" + } + ] + }, + "purgeOptions": { + "canPurgeByCpcode": false, + "canPurgeByCacheTag": false, + "cpcodeAccess": { + "allCurrentAndNewCpcodes": true, + "cpcodes": [] + } + }, + "credentials": [ + { + "credentialId": 456, + "clientToken": "akaa-bc78bc78bc78bc78-bc78bc78bc78bc78", + "status": "ACTIVE", + "createdOn": "2023-01-03T07:44:08.000Z", + "description": "desc", + "expiresOn": "2025-01-03T07:44:08.000Z", + "actions": { + "deactivate": true, + "delete": false, + "activate": false, + "editDescription": true, + "editExpiration": true + } + }, + { + "credentialId": 678, + "clientToken": "akaa-de90de90de90de90-de90de90de90de90", + "status": "INACTIVE", + "createdOn": "2023-01-03T07:44:08.000Z", + "description": "", + "expiresOn": "2025-01-03T07:44:08.000Z", + "actions": { + "deactivate": false, + "delete": false, + "activate": false, + "editDescription": false, + "editExpiration": false + } + }, + { + "credentialId": 789, + "clientToken": "akaa-gh34gh34gh34gh34-gh34gh34gh34gh34", + "status": "DELETED", + "createdOn": "2023-01-03T07:44:08.000Z", + "description": "", + "expiresOn": "2025-01-03T07:44:08.000Z", + "actions": { + "deactivate": false, + "delete": false, + "activate": false, + "editDescription": false, + "editExpiration": false + } + } + ], + "actions": { + "editGroups": true, + "editApis": true, + "lock": false, + "unlock": false, + "editAuth": true, + "edit": true, + "editSwitchAccount": false, + "transfer": true, + "editIpAcl": true, + "delete": false, + "deactivateAll": true + } +} +`, + expectedPath: "/identity-management/v3/api-clients/abcdefgh12345678?actions=true&apiAccess=true&credentials=true&groupAccess=true&ipAcl=true", + expectedResponse: &GetAPIClientResponse{ + AccessToken: "akaa-1a2b3c4d1a2b3c4d-1a2b3c4d1a2b3c4d", + BaseURL: "https://akaa-d4c3b2a1d4c3b2a1-d4c3b2a1d4c3b2a1.luna-dev.akamaiapis.net", + ActiveCredentialCount: 0, + AllowAccountSwitch: false, + AuthorizedUsers: []string{"user1"}, + CanAutoCreateCredential: false, + ClientDescription: "test_user_1 description", + ClientID: "abcdefgh12345678", + ClientName: "test_user_1", + ClientType: ClientClientType, + CreatedBy: "admin", + CreatedDate: test.NewTimeFromString(t, "2024-07-16T23:01:50.000Z"), + IsLocked: false, + NotificationEmails: []string{"user1@example.com"}, + GroupAccess: GroupAccess{ + CloneAuthorizedUserGroups: true, + Groups: []ClientGroup{ + { + GroupID: 123, + GroupName: "GroupName-G-R0UP", + RoleID: 1, + RoleName: "Admin", + RoleDescription: "This role provides the maximum access to users. An Administrator can perform admin tasks such as creating users and groups; configuration-related tasks such as creating and editing configurations; publishing tasks", + Subgroups: []ClientGroup{}, + }, + }, + }, + APIAccess: APIAccess{ + AllAccessibleAPIs: true, + APIs: []API{ + { + APIID: 1, + APIName: "API Client Administration", + Description: "API Client Administration", + Endpoint: "/identity-management", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadOnlyLevel, + }, + { + APIID: 2, + APIName: "CCU APIs", + Description: "Content control utility APIs", + Endpoint: "/ccu", + DocumentationURL: "https://developer.akamai.com", + AccessLevel: ReadWriteLevel, + }, + }, + }, + PurgeOptions: PurgeOptions{ + CanPurgeByCPCode: false, + CanPurgeByCacheTag: false, + CPCodeAccess: CPCodeAccess{ + AllCurrentAndNewCPCodes: true, + CPCodes: []int64{}, + }, + }, + Credentials: []APIClientCredential{ + { + Actions: CredentialActions{ + Deactivate: true, + Delete: false, + Activate: false, + EditDescription: true, + EditExpiration: true, + }, + ClientToken: "akaa-bc78bc78bc78bc78-bc78bc78bc78bc78", + CreatedOn: test.NewTimeFromString(t, "2023-01-03T07:44:08.000Z"), + CredentialID: 456, + Description: "desc", + ExpiresOn: test.NewTimeFromString(t, "2025-01-03T07:44:08.000Z"), + Status: CredentialActive, + }, + { + Actions: CredentialActions{ + Deactivate: false, + Delete: false, + Activate: false, + EditDescription: false, + EditExpiration: false, + }, + ClientToken: "akaa-de90de90de90de90-de90de90de90de90", + CreatedOn: test.NewTimeFromString(t, "2023-01-03T07:44:08.000Z"), + CredentialID: 678, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2025-01-03T07:44:08.000Z"), + Status: CredentialInactive, + }, + { + Actions: CredentialActions{ + Deactivate: false, + Delete: false, + Activate: false, + EditDescription: false, + EditExpiration: false, + }, + ClientToken: "akaa-gh34gh34gh34gh34-gh34gh34gh34gh34", + CreatedOn: test.NewTimeFromString(t, "2023-01-03T07:44:08.000Z"), + CredentialID: 789, + Description: "", + ExpiresOn: test.NewTimeFromString(t, "2025-01-03T07:44:08.000Z"), + Status: CredentialDeleted, + }, + }, + Actions: &APIClientActions{ + EditGroups: true, + EditAPIs: true, + Lock: false, + Unlock: false, + EditAuth: true, + Edit: true, + EditSwitchAccount: false, + Transfer: true, + EditIPAcl: true, + Delete: false, + DeactivateAll: true, + }, + }, + }, + "500 internal server error": { + params: GetAPIClientRequest{}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/api-clients/self?actions=false&apiAccess=false&credentials=false&groupAccess=false&ipAcl=false", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetAPIClient(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_DeleteAPIClient(t *testing.T) { + tests := map[string]struct { + params DeleteAPIClientRequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 No content - self": { + params: DeleteAPIClientRequest{}, + responseStatus: http.StatusNoContent, + expectedPath: "/identity-management/v3/api-clients/self", + }, + "204 No content - by ID": { + params: DeleteAPIClientRequest{ClientID: "abcdefgh12345678"}, + responseStatus: http.StatusNoContent, + expectedPath: "/identity-management/v3/api-clients/abcdefgh12345678", + }, + "500 internal server error": { + params: DeleteAPIClientRequest{ClientID: "abcdefgh12345678"}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/api-clients/abcdefgh12345678", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodDelete, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + err := client.DeleteAPIClient(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/iam/blocked_properties.go b/pkg/iam/blocked_properties.go index aa64d353..6720eac4 100644 --- a/pkg/iam/blocked_properties.go +++ b/pkg/iam/blocked_properties.go @@ -7,30 +7,18 @@ import ( "net/http" "net/url" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // BlockedProperties is the IAM user blocked properties API interface - BlockedProperties interface { - // ListBlockedProperties returns all properties a user doesn't have access to in a group - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-blocked-properties - ListBlockedProperties(context.Context, ListBlockedPropertiesRequest) ([]int64, error) - - // UpdateBlockedProperties removes or grant user access to properties - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-blocked-properties - UpdateBlockedProperties(context.Context, UpdateBlockedPropertiesRequest) ([]int64, error) - } - - // ListBlockedPropertiesRequest contains the request parameters for the list blocked properties endpoint + // ListBlockedPropertiesRequest contains the request parameters for the ListBlockedProperties endpoint. ListBlockedPropertiesRequest struct { IdentityID string GroupID int64 } - // UpdateBlockedPropertiesRequest contains the request parameters for the update blocked properties endpoint + // UpdateBlockedPropertiesRequest contains the request parameters for the UpdateBlockedProperties endpoint. UpdateBlockedPropertiesRequest struct { IdentityID string GroupID int64 @@ -39,27 +27,27 @@ type ( ) var ( - // ErrListBlockedProperties is returned when ListBlockedPropertiesRequest fails + // ErrListBlockedProperties is returned when ListBlockedPropertiesRequest fails. ErrListBlockedProperties = errors.New("list blocked properties") - // ErrUpdateBlockedProperties is returned when UpdateBlockedPropertiesRequest fails + // ErrUpdateBlockedProperties is returned when UpdateBlockedPropertiesRequest fails. ErrUpdateBlockedProperties = errors.New("update blocked properties") ) -// Validate validates ListBlockedPropertiesRequest +// Validate validates ListBlockedPropertiesRequest. func (r ListBlockedPropertiesRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), "GroupID": validation.Validate(r.GroupID, validation.Required), - }.Filter() + }) } -// Validate validates UpdateBlockedPropertiesRequest +// Validate validates UpdateBlockedPropertiesRequest. func (r UpdateBlockedPropertiesRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), "GroupID": validation.Validate(r.GroupID, validation.Required), - }.Filter() + }) } func (i *iam) ListBlockedProperties(ctx context.Context, params ListBlockedPropertiesRequest) ([]int64, error) { @@ -67,12 +55,12 @@ func (i *iam) ListBlockedProperties(ctx context.Context, params ListBlockedPrope return nil, fmt.Errorf("%s: %w:\n%s", ErrListBlockedProperties, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/groups/%d/blocked-properties", params.IdentityID, params.GroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/groups/%d/blocked-properties", params.IdentityID, params.GroupID)) if err != nil { return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListBlockedProperties, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListBlockedProperties, err) } @@ -95,12 +83,12 @@ func (i *iam) UpdateBlockedProperties(ctx context.Context, params UpdateBlockedP return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateBlockedProperties, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/groups/%d/blocked-properties", params.IdentityID, params.GroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/groups/%d/blocked-properties", params.IdentityID, params.GroupID)) if err != nil { return nil, fmt.Errorf("%w: failed to parse url: %s", ErrUpdateBlockedProperties, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateBlockedProperties, err) } diff --git a/pkg/iam/blocked_properties_test.go b/pkg/iam/blocked_properties_test.go index e0ece149..cf243d91 100644 --- a/pkg/iam/blocked_properties_test.go +++ b/pkg/iam/blocked_properties_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIam_ListBlockedProperties(t *testing.T) { +func TestIAM_ListBlockedProperties(t *testing.T) { tests := map[string]struct { params ListBlockedPropertiesRequest responseStatus int @@ -25,7 +25,7 @@ func TestIam_ListBlockedProperties(t *testing.T) { IdentityID: "1-ABCDE", }, responseStatus: http.StatusOK, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", responseBody: `[ 10977166 ]`, @@ -39,7 +39,7 @@ func TestIam_ListBlockedProperties(t *testing.T) { IdentityID: "1-ABCDE", }, responseStatus: http.StatusOK, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", responseBody: `[ ]`, @@ -52,7 +52,7 @@ func TestIam_ListBlockedProperties(t *testing.T) { IdentityID: "1-ABCDE", }, responseStatus: http.StatusNotFound, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/123450000/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/123450000/blocked-properties", responseBody: ` { "instance": "", @@ -80,7 +80,7 @@ func TestIam_ListBlockedProperties(t *testing.T) { IdentityID: "1-ABCDE", }, responseStatus: http.StatusInternalServerError, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", responseBody: ` { "type": "internal_error", @@ -99,28 +99,28 @@ func TestIam_ListBlockedProperties(t *testing.T) { }, }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - users, err := client.ListBlockedProperties(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + users, err := client.ListBlockedProperties(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) - assert.Equal(t, test.expectedResponse, users) + assert.Equal(t, tc.expectedResponse, users) }) } } -func TestIam_UpdateBlockedProperties(t *testing.T) { +func TestIAM_UpdateBlockedProperties(t *testing.T) { tests := map[string]struct { params UpdateBlockedPropertiesRequest responseStatus int @@ -136,7 +136,7 @@ func TestIam_UpdateBlockedProperties(t *testing.T) { Properties: []int64{10977166, 10977167}, }, responseStatus: http.StatusOK, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", responseBody: `[ 10977166,10977167 ]`, @@ -151,7 +151,7 @@ func TestIam_UpdateBlockedProperties(t *testing.T) { Properties: []int64{0, 1}, }, responseStatus: http.StatusNotFound, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", responseBody: ` { "instance": "", @@ -180,7 +180,7 @@ func TestIam_UpdateBlockedProperties(t *testing.T) { Properties: []int64{10977166, 10977167}, }, responseStatus: http.StatusNotFound, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/123450000/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/123450000/blocked-properties", responseBody: ` { "instance": "", @@ -208,7 +208,7 @@ func TestIam_UpdateBlockedProperties(t *testing.T) { IdentityID: "1-ABCDE", }, responseStatus: http.StatusInternalServerError, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/groups/12345/blocked-properties", responseBody: ` { "type": "internal_error", @@ -227,23 +227,23 @@ func TestIam_UpdateBlockedProperties(t *testing.T) { }, }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - users, err := client.UpdateBlockedProperties(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + users, err := client.UpdateBlockedProperties(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) - assert.Equal(t, test.expectedResponse, users) + assert.Equal(t, tc.expectedResponse, users) }) } } diff --git a/pkg/iam/cidr.go b/pkg/iam/cidr.go new file mode 100644 index 00000000..ed28b947 --- /dev/null +++ b/pkg/iam/cidr.go @@ -0,0 +1,341 @@ +package iam + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // ListCIDRBlocksRequest contains the request parameters for the ListCIDRBlocks endpoint. + ListCIDRBlocksRequest struct { + Actions bool + } + + // ListCIDRBlocksResponse describes the response from the ListCIDRBlocks endpoint. + ListCIDRBlocksResponse []CIDRBlock + + // CIDRBlock represents a CIDR block. + CIDRBlock struct { + Actions *CIDRActions `json:"actions"` + CIDRBlock string `json:"cidrBlock"` + CIDRBlockID int64 `json:"cidrBlockId"` + Comments *string `json:"comments"` + CreatedBy string `json:"createdBy"` + CreatedDate time.Time `json:"createdDate"` + Enabled bool `json:"enabled"` + ModifiedBy string `json:"modifiedBy"` + ModifiedDate time.Time `json:"modifiedDate"` + } + + // CIDRActions specifies activities available for the CIDR block. + CIDRActions struct { + Delete bool `json:"delete"` + Edit bool `json:"edit"` + } + + // CreateCIDRBlockRequest contains the request parameters for the CreateCIDRBlock endpoint. + CreateCIDRBlockRequest struct { + CIDRBlock string `json:"cidrBlock"` + Comments *string `json:"comments,omitempty"` + Enabled bool `json:"enabled"` + } + + // CreateCIDRBlockResponse describes the response from the CreateCIDRBlock endpoint. + CreateCIDRBlockResponse CIDRBlock + + // GetCIDRBlockRequest contains the request parameters for the GetCIDRBlock endpoint. + GetCIDRBlockRequest struct { + CIDRBlockID int64 + Actions bool + } + + // GetCIDRBlockResponse describes the response from the GetCIDRBlock endpoint. + GetCIDRBlockResponse CIDRBlock + + // UpdateCIDRBlockRequest contains the request parameters for the UpdateCIDRBlock endpoint. + UpdateCIDRBlockRequest struct { + CIDRBlockID int64 + Body UpdateCIDRBlockRequestBody + } + + // UpdateCIDRBlockRequestBody contains the request body to be used in the UpdateCIDRBlock endpoint. + UpdateCIDRBlockRequestBody struct { + CIDRBlock string `json:"cidrBlock"` + Comments *string `json:"comments,omitempty"` + Enabled bool `json:"enabled"` + } + + // UpdateCIDRBlockResponse describes the response of the UpdateCIDRBlock endpoint. + UpdateCIDRBlockResponse CIDRBlock + + // DeleteCIDRBlockRequest contains the request parameters for the DeleteCIDRBlock endpoint. + DeleteCIDRBlockRequest struct { + CIDRBlockID int64 + } + + // ValidateCIDRBlockRequest contains the request parameters for the ValidateCIDRBlock endpoint. + ValidateCIDRBlockRequest struct { + CIDRBlock string + } +) + +// validateCIDR validates the format of CIDRBlock. +func validateCIDR() validation.Rule { + return validation.By(func(value interface{}) error { + stringVal, ok := value.(string) + if !ok { + return fmt.Errorf("expected type 'string', got: %T", value) + } + + _, _, err := net.ParseCIDR(stringVal) + if err != nil { + return err + } + + return nil + }) +} + +// Validate validates validation on CreateCIDRBlockRequest. +func (r CreateCIDRBlockRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CIDRBlock": validation.Validate(r.CIDRBlock, validation.Required, validateCIDR()), + }) +} + +// Validate validates validation on GetCIDRBlockRequest. +func (r GetCIDRBlockRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CIDRBlockID": validation.Validate(r.CIDRBlockID, validation.Required, validation.Min(1)), + }) +} + +// Validate validates validation on UpdateCIDRBlockRequest. +func (r UpdateCIDRBlockRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CIDRBlockID": validation.Validate(r.CIDRBlockID, validation.Required, validation.Min(1)), + "Body": validation.Validate(r.Body, validation.Required), + }) +} + +// Validate validates validation on UpdateCIDRBlockRequestBody. +func (r UpdateCIDRBlockRequestBody) Validate() error { + return validation.Errors{ + "CIDRBlock": validation.Validate(r.CIDRBlock, validation.Required, validateCIDR()), + }.Filter() +} + +// Validate validates validation on DeleteCIDRBlockRequest. +func (r DeleteCIDRBlockRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CIDRBlockID": validation.Validate(r.CIDRBlockID, validation.Required, validation.Min(1)), + }) +} + +// Validate validates validation on ValidateCIDRBlockRequest. +func (r ValidateCIDRBlockRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "CIDRBlock": validation.Validate(r.CIDRBlock, validation.Required, validateCIDR()), + }) +} + +var ( + // ErrListCIDRBlocks is returned when ListCIDRBlocks fails. + ErrListCIDRBlocks = errors.New("list CIDR blocks") + // ErrCreateCIDRBlock is returned when CreateCIDRBlock fails. + ErrCreateCIDRBlock = errors.New("create CIDR block") + // ErrGetCIDRBlock is returned when GetCIDRBlock fails. + ErrGetCIDRBlock = errors.New("get CIDR block") + // ErrUpdateCIDRBlock is returned when UpdateCIDRBlock fails. + ErrUpdateCIDRBlock = errors.New("update CIDR block") + // ErrDeleteCIDRBlock is returned when DeleteCIDRBlock fails. + ErrDeleteCIDRBlock = errors.New("delete CIDR block") + // ErrValidateCIDRBlock is returned when ValidateCIDRBlock fails. + ErrValidateCIDRBlock = errors.New("validate CIDR block") +) + +func (i *iam) ListCIDRBlocks(ctx context.Context, params ListCIDRBlocksRequest) (ListCIDRBlocksResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListCIDRBlocks") + + uri, err := url.Parse("/identity-management/v3/user-admin/ip-acl/allowlist") + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListCIDRBlocks, err) + } + + q := uri.Query() + q.Add("actions", strconv.FormatBool(params.Actions)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListCIDRBlocks, err) + } + + var result ListCIDRBlocksResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListCIDRBlocks, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListCIDRBlocks, i.Error(resp)) + } + + return result, nil +} + +func (i *iam) CreateCIDRBlock(ctx context.Context, params CreateCIDRBlockRequest) (*CreateCIDRBlockResponse, error) { + logger := i.Log(ctx) + logger.Debug("CreateCIDRBlock") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrCreateCIDRBlock, ErrStructValidation, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/identity-management/v3/user-admin/ip-acl/allowlist", nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateCIDRBlock, err) + } + + var result CreateCIDRBlockResponse + resp, err := i.Exec(req, &result, params) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrCreateCIDRBlock, err) + } + + if resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("%s: %w", ErrCreateCIDRBlock, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) GetCIDRBlock(ctx context.Context, params GetCIDRBlockRequest) (*GetCIDRBlockResponse, error) { + logger := i.Log(ctx) + logger.Debug("GetCIDRBlock") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetCIDRBlock, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ip-acl/allowlist/%d", params.CIDRBlockID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrGetCIDRBlock, err) + } + + q := uri.Query() + q.Add("actions", strconv.FormatBool(params.Actions)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetCIDRBlock, err) + } + + var result GetCIDRBlockResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrGetCIDRBlock, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrGetCIDRBlock, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) UpdateCIDRBlock(ctx context.Context, params UpdateCIDRBlockRequest) (*UpdateCIDRBlockResponse, error) { + logger := i.Log(ctx) + logger.Debug("UpdateCIDRBlock") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrUpdateCIDRBlock, ErrStructValidation, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("/identity-management/v3/user-admin/ip-acl/allowlist/%d", params.CIDRBlockID), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateCIDRBlock, err) + } + + var result UpdateCIDRBlockResponse + resp, err := i.Exec(req, &result, params.Body) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateCIDRBlock, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrUpdateCIDRBlock, i.Error(resp)) + } + + return &result, nil +} + +func (i *iam) DeleteCIDRBlock(ctx context.Context, params DeleteCIDRBlockRequest) error { + logger := i.Log(ctx) + logger.Debug("DeleteCIDRBlock") + + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrDeleteCIDRBlock, ErrStructValidation, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("/identity-management/v3/user-admin/ip-acl/allowlist/%d", params.CIDRBlockID), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrDeleteCIDRBlock, err) + } + + resp, err := i.Exec(req, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrDeleteCIDRBlock, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrDeleteCIDRBlock, i.Error(resp)) + } + + return nil +} + +func (i *iam) ValidateCIDRBlock(ctx context.Context, params ValidateCIDRBlockRequest) error { + logger := i.Log(ctx) + logger.Debug("ValidateCIDRBlock") + + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w: %s", ErrValidateCIDRBlock, ErrStructValidation, err) + } + + uri, err := url.Parse("/identity-management/v3/user-admin/ip-acl/allowlist/validate") + if err != nil { + return fmt.Errorf("%w: failed to parse url: %s", ErrValidateCIDRBlock, err) + } + + q := uri.Query() + q.Add("cidrblock", params.CIDRBlock) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrValidateCIDRBlock, err) + } + + resp, err := i.Exec(req, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrValidateCIDRBlock, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrValidateCIDRBlock, i.Error(resp)) + } + + return nil +} diff --git a/pkg/iam/cidr_test.go b/pkg/iam/cidr_test.go new file mode 100644 index 00000000..b11234d2 --- /dev/null +++ b/pkg/iam/cidr_test.go @@ -0,0 +1,806 @@ +package iam + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIAM_ListCIDRBlocks(t *testing.T) { + tests := map[string]struct { + params ListCIDRBlocksRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse ListCIDRBlocksResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: ListCIDRBlocksRequest{}, + responseStatus: http.StatusOK, + responseBody: ` +[ + { + "cidrBlockId": 1, + "enabled": true, + "comments": "abc", + "cidrBlock": "1.2.3.4/8", + "createdDate": "2024-06-17T08:46:41.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-06-17T08:46:41.000Z", + "modifiedBy": "johndoe" + }, + { + "cidrBlockId": 2, + "enabled": false, + "comments": null, + "cidrBlock": "2.4.8.16/32", + "createdDate": "2024-06-25T06:14:36.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-06-25T06:14:36.000Z", + "modifiedBy": "johndoe" + } +]`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist?actions=false", + expectedResponse: ListCIDRBlocksResponse{ + { + CIDRBlockID: 1, + Enabled: true, + Comments: ptr.To("abc"), + CIDRBlock: "1.2.3.4/8", + CreatedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + ModifiedBy: "johndoe", + Actions: nil, + }, + { + CIDRBlockID: 2, + Enabled: false, + Comments: nil, + CIDRBlock: "2.4.8.16/32", + CreatedDate: test.NewTimeFromString(t, "2024-06-25T06:14:36.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-06-25T06:14:36.000Z"), + ModifiedBy: "johndoe", + Actions: nil, + }, + }, + }, + "200 with actions": { + params: ListCIDRBlocksRequest{Actions: true}, + responseStatus: http.StatusOK, + responseBody: ` +[ + { + "cidrBlockId": 1, + "enabled": true, + "comments": "abc", + "cidrBlock": "1.2.3.4/8", + "createdDate": "2024-06-17T08:46:41.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-06-17T08:46:41.000Z", + "modifiedBy": "johndoe", + "actions": { + "edit": true, + "delete": true + } + }, + { + "cidrBlockId": 2, + "enabled": false, + "comments": null, + "cidrBlock": "2.4.8.16/32", + "createdDate": "2024-06-25T06:14:36.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-06-25T06:14:36.000Z", + "modifiedBy": "johndoe", + "actions": { + "edit": true, + "delete": true + } + } +]`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist?actions=true", + expectedResponse: ListCIDRBlocksResponse{ + { + CIDRBlockID: 1, + Enabled: true, + Comments: ptr.To("abc"), + CIDRBlock: "1.2.3.4/8", + CreatedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + ModifiedBy: "johndoe", + Actions: &CIDRActions{ + Edit: true, + Delete: true, + }, + }, + { + CIDRBlockID: 2, + Enabled: false, + Comments: nil, + CIDRBlock: "2.4.8.16/32", + CreatedDate: test.NewTimeFromString(t, "2024-06-25T06:14:36.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-06-25T06:14:36.000Z"), + ModifiedBy: "johndoe", + Actions: &CIDRActions{ + Edit: true, + Delete: true, + }, + }, + }, + }, + "500 internal server error": { + params: ListCIDRBlocksRequest{}, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +} +`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist?actions=false", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + result, err := client.ListCIDRBlocks(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_CreateCIDRBlock(t *testing.T) { + tests := map[string]struct { + params CreateCIDRBlockRequest + responseStatus int + responseBody string + expectedPath string + expectedRequestBody string + expectedResponse *CreateCIDRBlockResponse + withError func(*testing.T, error) + }{ + "201 created": { + params: CreateCIDRBlockRequest{ + CIDRBlock: "1.2.3.4/32", + Comments: ptr.To("abc"), + Enabled: true, + }, + responseStatus: http.StatusCreated, + responseBody: ` +{ + "cidrBlockId": 1234, + "enabled": true, + "comments": "abc", + "cidrBlock": "1.2.3.4/32", + "createdDate": "2024-07-15T13:53:49.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-07-15T13:53:49.000Z", + "modifiedBy": "johndoe" +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist", + expectedRequestBody: `{"cidrBlock":"1.2.3.4/32","comments":"abc","enabled":true}`, + expectedResponse: &CreateCIDRBlockResponse{ + CIDRBlockID: 1234, + Enabled: true, + Comments: ptr.To("abc"), + CIDRBlock: "1.2.3.4/32", + CreatedDate: test.NewTimeFromString(t, "2024-07-15T13:53:49.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-07-15T13:53:49.000Z"), + ModifiedBy: "johndoe", + Actions: nil, + }, + }, + "201 without comment": { + params: CreateCIDRBlockRequest{ + CIDRBlock: "1.2.3.4/32", + Enabled: true, + }, + responseStatus: http.StatusCreated, + responseBody: ` +{ + "cidrBlockId": 1234, + "enabled": true, + "comments": null, + "cidrBlock": "1.2.3.4/32", + "createdDate": "2024-07-15T13:53:49.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-07-15T13:53:49.000Z", + "modifiedBy": "johndoe" +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist", + expectedRequestBody: `{"cidrBlock":"1.2.3.4/32","enabled":true}`, + expectedResponse: &CreateCIDRBlockResponse{ + CIDRBlockID: 1234, + Enabled: true, + Comments: nil, + CIDRBlock: "1.2.3.4/32", + CreatedDate: test.NewTimeFromString(t, "2024-07-15T13:53:49.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-07-15T13:53:49.000Z"), + ModifiedBy: "johndoe", + Actions: nil, + }, + }, + "missing required parameters": { + params: CreateCIDRBlockRequest{}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "create CIDR block: struct validation: CIDRBlock: cannot be blank") + }, + }, + "validation error - incorrect IPv4 CIDR": { + params: CreateCIDRBlockRequest{ + CIDRBlock: "1.2.3.555/32", + }, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "create CIDR block: struct validation: CIDRBlock: invalid CIDR address: 1.2.3.555/32") + }, + }, + "validation error - incorrect IPv6 CIDR": { + params: CreateCIDRBlockRequest{ + CIDRBlock: "aa::wqert:1:0:ff/48", + }, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "create CIDR block: struct validation: CIDRBlock: invalid CIDR address: aa::wqert:1:0:ff/48") + }, + }, + "500 internal server error": { + params: CreateCIDRBlockRequest{ + CIDRBlock: "1.2.3.4/32", + Comments: ptr.To("abc"), + Enabled: true, + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +} +`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.Equal(t, tc.expectedRequestBody, string(body)) + } + })) + client := mockAPIClient(t, mockServer) + result, err := client.CreateCIDRBlock(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_GetCIDRBlock(t *testing.T) { + tests := map[string]struct { + params GetCIDRBlockRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetCIDRBlockResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: GetCIDRBlockRequest{CIDRBlockID: 1}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "cidrBlockId": 1, + "enabled": true, + "comments": "abc", + "cidrBlock": "1.2.3.4/8", + "createdDate": "2024-06-17T08:46:41.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-06-17T08:46:41.000Z", + "modifiedBy": "johndoe" +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1?actions=false", + expectedResponse: &GetCIDRBlockResponse{ + CIDRBlockID: 1, + Enabled: true, + Comments: ptr.To("abc"), + CIDRBlock: "1.2.3.4/8", + CreatedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + ModifiedBy: "johndoe", + Actions: nil, + }, + }, + "200 with actions": { + params: GetCIDRBlockRequest{CIDRBlockID: 1, Actions: true}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "cidrBlockId": 1, + "enabled": true, + "comments": "abc", + "cidrBlock": "1.2.3.4/8", + "createdDate": "2024-06-17T08:46:41.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-06-17T08:46:41.000Z", + "modifiedBy": "johndoe", + "actions": { + "edit": true, + "delete": true + } +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1?actions=true", + expectedResponse: &GetCIDRBlockResponse{ + CIDRBlockID: 1, + Enabled: true, + Comments: ptr.To("abc"), + CIDRBlock: "1.2.3.4/8", + CreatedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-06-17T08:46:41.000Z"), + ModifiedBy: "johndoe", + Actions: &CIDRActions{ + Edit: true, + Delete: true, + }, + }, + }, + "missing required parameters": { + params: GetCIDRBlockRequest{}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "get CIDR block: struct validation: CIDRBlockID: cannot be blank") + }, + }, + "incorrect parameters": { + params: GetCIDRBlockRequest{CIDRBlockID: -1}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "get CIDR block: struct validation: CIDRBlockID: must be no less than 1") + }, + }, + "404 no such block": { + params: GetCIDRBlockRequest{CIDRBlockID: 9000}, + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/ip-acl/error-types/1010", + "httpStatus": 404, + "title": "no data found", + "detail": "", + "instance": "", + "errors": [] +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/9000?actions=false", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "/ip-acl/error-types/1010", + HTTPStatus: http.StatusNotFound, + Title: "no data found", + Detail: "", + StatusCode: http.StatusNotFound, + Instance: "", + Errors: json.RawMessage("[]"), + } + assert.Equal(t, true, err.Is(e)) + }, + }, + "500 internal server error": { + params: GetCIDRBlockRequest{CIDRBlockID: 1}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1?actions=false", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetCIDRBlock(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_UpdateCIDRBlock(t *testing.T) { + tests := map[string]struct { + params UpdateCIDRBlockRequest + responseStatus int + responseBody string + expectedPath string + expectedRequestBody string + expectedResponse *UpdateCIDRBlockResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: UpdateCIDRBlockRequest{ + CIDRBlockID: 1, + Body: UpdateCIDRBlockRequestBody{ + CIDRBlock: "1.2.3.4/32", + Comments: ptr.To("abc - updated"), + Enabled: false, + }, + }, + responseStatus: http.StatusOK, + responseBody: ` +{ + "cidrBlockId": 1234, + "enabled": false, + "comments": "abc - updated", + "cidrBlock": "1.2.3.4/32", + "createdDate": "2024-07-15T13:53:49.000Z", + "createdBy": "johndoe", + "modifiedDate": "2024-07-16T13:53:49.000Z", + "modifiedBy": "johndoe" +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1", + expectedRequestBody: `{"cidrBlock":"1.2.3.4/32","comments":"abc - updated","enabled":false}`, + expectedResponse: &UpdateCIDRBlockResponse{ + CIDRBlockID: 1234, + Enabled: false, + Comments: ptr.To("abc - updated"), + CIDRBlock: "1.2.3.4/32", + CreatedDate: test.NewTimeFromString(t, "2024-07-15T13:53:49.000Z"), + CreatedBy: "johndoe", + ModifiedDate: test.NewTimeFromString(t, "2024-07-16T13:53:49.000Z"), + ModifiedBy: "johndoe", + Actions: nil, + }, + }, + "missing required parameters": { + params: UpdateCIDRBlockRequest{}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "update CIDR block: struct validation: CIDRBlock: cannot be blank\nCIDRBlockID: cannot be blank") + }, + }, + "invalid required parameters": { + params: UpdateCIDRBlockRequest{ + CIDRBlockID: -1, + Body: UpdateCIDRBlockRequestBody{ + CIDRBlock: "1.2.3.4/32", + Comments: ptr.To("abc - updated"), + Enabled: false, + }, + }, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "update CIDR block: struct validation: CIDRBlockID: must be no less than 1") + }, + }, + "validation error - invalid IP address": { + params: UpdateCIDRBlockRequest{ + CIDRBlockID: 1, + Body: UpdateCIDRBlockRequestBody{ + CIDRBlock: "1a.2.3.4/32", + }, + }, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "update CIDR block: struct validation: CIDRBlock: invalid CIDR address: 1a.2.3.4/32") + }, + }, + "500 internal server error": { + params: UpdateCIDRBlockRequest{ + CIDRBlockID: 1, + Body: UpdateCIDRBlockRequestBody{ + CIDRBlock: "1.2.3.4/32", + Comments: ptr.To("abc - updated"), + Enabled: false, + }, + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.Equal(t, tc.expectedRequestBody, string(body)) + } + })) + client := mockAPIClient(t, mockServer) + result, err := client.UpdateCIDRBlock(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_DeleteCIDRBlock(t *testing.T) { + tests := map[string]struct { + params DeleteCIDRBlockRequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 No content": { + params: DeleteCIDRBlockRequest{CIDRBlockID: 1}, + responseStatus: http.StatusNoContent, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1", + }, + "missing required parameters": { + params: DeleteCIDRBlockRequest{}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "delete CIDR block: struct validation: CIDRBlockID: cannot be blank") + }, + }, + "incorrect parameters": { + params: DeleteCIDRBlockRequest{CIDRBlockID: -1}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "delete CIDR block: struct validation: CIDRBlockID: must be no less than 1") + }, + }, + "404 no such block": { + params: DeleteCIDRBlockRequest{CIDRBlockID: 9000}, + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/ip-acl/error-types/1010", + "httpStatus": 404, + "title": "no data found", + "detail": "", + "instance": "", + "errors": [] +}`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/9000", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "/ip-acl/error-types/1010", + HTTPStatus: http.StatusNotFound, + Title: "no data found", + Detail: "", + StatusCode: http.StatusNotFound, + Instance: "", + Errors: json.RawMessage("[]"), + } + assert.Equal(t, true, err.Is(e)) + }, + }, + "500 internal server error": { + params: DeleteCIDRBlockRequest{CIDRBlockID: 1}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/1", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodDelete, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + err := client.DeleteCIDRBlock(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_ValidateCIDRBlock(t *testing.T) { + tests := map[string]struct { + params ValidateCIDRBlockRequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 No content": { + params: ValidateCIDRBlockRequest{CIDRBlock: "1.2.3.4/32"}, + responseStatus: http.StatusNoContent, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/validate?cidrblock=1.2.3.4%2F32", + }, + "missing required parameters": { + params: ValidateCIDRBlockRequest{}, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "validate CIDR block: struct validation: CIDRBlock: cannot be blank") + }, + }, + "validation error - invalid IP address": { + params: ValidateCIDRBlockRequest{ + CIDRBlock: "255.255.255.256/24", + }, + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + assert.Contains(t, err.Error(), "validate CIDR block: struct validation: CIDRBlock: invalid CIDR address: 255.255.255.256/24") + }, + }, + "500 internal server error": { + params: ValidateCIDRBlockRequest{CIDRBlock: "1.2.3.4/32"}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + } + `, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/validate?cidrblock=1.2.3.4%2F32", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + + })) + client := mockAPIClient(t, mockServer) + err := client.ValidateCIDRBlock(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/iam/errors.go b/pkg/iam/errors.go index b2563e96..84a9b3ea 100644 --- a/pkg/iam/errors.go +++ b/pkg/iam/errors.go @@ -4,14 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( - // Error is an IAM error interface + // Error is an IAM error interface. Error struct { Type string `json:"type"` Title string `json:"title"` @@ -26,18 +26,13 @@ type ( } ) -var ( - // ErrInputValidation is returned when the input parameters failed validation - ErrInputValidation = errors.New("input validation error") -) - -// Error parses an error from the response +// Error parses an error from the response. func (i *iam) Error(r *http.Response) error { var e Error var body []byte - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { i.Log(r.Request.Context()).Errorf("reading error response body: %s", err) e.StatusCode = r.StatusCode @@ -65,7 +60,7 @@ func (e *Error) Error() string { return fmt.Sprintf("API error: \n%s", msg) } -// Is handles error comparisons +// Is handles error comparisons. func (e *Error) Is(target error) bool { var t *Error if !errors.As(target, &t) { diff --git a/pkg/iam/errors_test.go b/pkg/iam/errors_test.go index b1e435b7..827776d1 100644 --- a/pkg/iam/errors_test.go +++ b/pkg/iam/errors_test.go @@ -2,12 +2,12 @@ package iam import ( "context" - "io/ioutil" + "io" "net/http" "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -31,7 +31,7 @@ func TestNewError(t *testing.T) { response: &http.Response{ Status: "Internal Server Error", StatusCode: http.StatusInternalServerError, - Body: ioutil.NopCloser(strings.NewReader( + Body: io.NopCloser(strings.NewReader( `{"type":"a","title":"b","detail":"c"}`), ), Request: req, @@ -47,7 +47,7 @@ func TestNewError(t *testing.T) { response: &http.Response{ Status: "Internal Server Error", StatusCode: http.StatusInternalServerError, - Body: ioutil.NopCloser(strings.NewReader( + Body: io.NopCloser(strings.NewReader( `test`), ), Request: req, @@ -59,10 +59,10 @@ func TestNewError(t *testing.T) { }, }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { - res := Client(sess).(*iam).Error(test.response) - assert.Equal(t, test.expected, res) + res := Client(sess).(*iam).Error(tc.response) + assert.Equal(t, tc.expected, res) }) } } @@ -83,7 +83,7 @@ func TestJsonErrorUnmarshalling(t *testing.T) { Request: req, Status: "OK", StatusCode: http.StatusServiceUnavailable, - Body: ioutil.NopCloser(strings.NewReader(`......`))}, + Body: io.NopCloser(strings.NewReader(`......`))}, expected: &Error{ Type: "", StatusCode: http.StatusServiceUnavailable, @@ -96,7 +96,7 @@ func TestJsonErrorUnmarshalling(t *testing.T) { Request: req, Status: "OK", StatusCode: http.StatusServiceUnavailable, - Body: ioutil.NopCloser(strings.NewReader("Your request did not succeed as this operation has reached the limit for your account. Please try after 2024-01-16T15:20:55.945Z"))}, + Body: io.NopCloser(strings.NewReader("Your request did not succeed as this operation has reached the limit for your account. Please try after 2024-01-16T15:20:55.945Z"))}, expected: &Error{ Type: "", StatusCode: http.StatusServiceUnavailable, @@ -109,7 +109,7 @@ func TestJsonErrorUnmarshalling(t *testing.T) { Request: req, Status: "OK", StatusCode: http.StatusServiceUnavailable, - Body: ioutil.NopCloser(strings.NewReader(``))}, + Body: io.NopCloser(strings.NewReader(``))}, expected: &Error{ Type: "", Title: "Failed to unmarshal error body. IAM API failed. Check details for more information.", @@ -119,13 +119,13 @@ func TestJsonErrorUnmarshalling(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { sess, _ := session.New() i := iam{ Session: sess, } - assert.Equal(t, test.expected, i.Error(test.input)) + assert.Equal(t, tc.expected, i.Error(tc.input)) }) } } diff --git a/pkg/iam/groups.go b/pkg/iam/groups.go index cc0d5b80..95f4d63d 100644 --- a/pkg/iam/groups.go +++ b/pkg/iam/groups.go @@ -7,176 +7,140 @@ import ( "net/http" "net/url" "strconv" + "time" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Groups is the IAM group API interface - Groups interface { - // CreateGroup creates a new group within a parent group_id specified in the request - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-group - CreateGroup(context.Context, GroupRequest) (*Group, error) - - // GetGroup returns a group's details - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-group - GetGroup(context.Context, GetGroupRequest) (*Group, error) - - // ListAffectedUsers lists users who are affected when a group is moved - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-move-affected-users - ListAffectedUsers(context.Context, ListAffectedUsersRequest) ([]GroupUser, error) - - // ListGroups lists all groups in which you have a scope of admin for the current account and contract type - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-groups - ListGroups(context.Context, ListGroupsRequest) ([]Group, error) - - // RemoveGroup removes a group based on group_id. We can only delete a sub-group, and only if that sub-group doesn't include any users - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/delete-group - RemoveGroup(context.Context, RemoveGroupRequest) error - - // UpdateGroupName changes the name of the group - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-group - UpdateGroupName(context.Context, GroupRequest) (*Group, error) - - // MoveGroup Move a nested group under another group within the same parent hierarchy - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-groups-move - MoveGroup(context.Context, MoveGroupRequest) error - } - - // GetGroupRequest describes the request parameters of the get group endpoint + // GetGroupRequest describes the request parameters for the GetGroup endpoint. GetGroupRequest struct { GroupID int64 Actions bool } - // Group describes the response of the list groups endpoint + // Group describes the response from the ListGroups endpoint. Group struct { Actions *GroupActions `json:"actions,omitempty"` CreatedBy string `json:"createdBy"` - CreatedDate string `json:"createdDate"` + CreatedDate time.Time `json:"createdDate"` GroupID int64 `json:"groupId"` GroupName string `json:"groupName"` ModifiedBy string `json:"modifiedBy"` - ModifiedDate string `json:"modifiedDate"` + ModifiedDate time.Time `json:"modifiedDate"` ParentGroupID int64 `json:"parentGroupId"` SubGroups []Group `json:"subGroups,omitempty"` } - // GroupActions encapsulates permissions available to the user for this group + // GroupActions encapsulates permissions available to the user for this group. GroupActions struct { Delete bool `json:"delete"` Edit bool `json:"edit"` } - // GroupUser describes the response of the list affected users endpoint + // GroupUser describes the response from the ListAffectedUsers endpoint. GroupUser struct { - AccountID string `json:"accountId"` - Email string `json:"email"` - FirstName string `json:"firstName"` - IdentityID string `json:"uiIdentityId"` - LastLoginDate string `json:"lastLoginDate"` - LastName string `json:"lastName"` - UserName string `json:"uiUserName"` + AccountID string `json:"accountId"` + Email string `json:"email"` + FirstName string `json:"firstName"` + IdentityID string `json:"uiIdentityId"` + LastLoginDate time.Time `json:"lastLoginDate"` + LastName string `json:"lastName"` + UserName string `json:"uiUserName"` } - // GroupRequest describes the request and body parameters for creating new group or updating a group name endpoint + // GroupRequest describes the request and body parameters for creating new group or updating a group name endpoint. GroupRequest struct { GroupID int64 `json:"-"` GroupName string `json:"groupName"` } - // MoveGroupRequest describes the request body to move a group under another group + // MoveGroupRequest describes the request body for the MoveGroup endpoint. MoveGroupRequest struct { SourceGroupID int64 `json:"sourceGroupId"` DestinationGroupID int64 `json:"destinationGroupId"` } - // ListAffectedUsersRequest describes the request and body parameters of the list affected users endpoint + // ListAffectedUsersRequest describes the request and body parameters of the ListAffectedUsers endpoint. ListAffectedUsersRequest struct { DestinationGroupID int64 SourceGroupID int64 UserType string } - // ListGroupsRequest describes the request parameters of the list groups endpoint + // ListGroupsRequest describes the request parameters of the ListGroups endpoint. ListGroupsRequest struct { Actions bool } - // RemoveGroupRequest describes the request parameter for removing a group + // RemoveGroupRequest describes the request parameter for the RemoveGroup endpoint. RemoveGroupRequest struct { GroupID int64 } ) const ( - // LostAccessUsers with a userType of lostAccess lose their access to the source group + // LostAccessUsers with a userType of lostAccess lose their access to the source group. LostAccessUsers = "lostAccess" - // GainAccessUsers with a userType of gainAccess gain their access to the source group + // GainAccessUsers with a userType of gainAccess gain their access to the source group. GainAccessUsers = "gainAccess" ) var ( - // ErrCreateGroup is returned when CreateGroup fails + // ErrCreateGroup is returned when CreateGroup fails. ErrCreateGroup = errors.New("create group") - // ErrGetGroup is returned when GetGroup fails + // ErrGetGroup is returned when GetGroup fails. ErrGetGroup = errors.New("get group") - // ErrListAffectedUsers is returned when ListAffectedUsers fails + // ErrListAffectedUsers is returned when ListAffectedUsers fails. ErrListAffectedUsers = errors.New("list affected users") - // ErrListGroups is returned when ListGroups fails + // ErrListGroups is returned when ListGroups fails. ErrListGroups = errors.New("list groups") - // ErrUpdateGroupName is returned when UpdateGroupName fails + // ErrUpdateGroupName is returned when UpdateGroupName fails. ErrUpdateGroupName = errors.New("update group name") - // ErrRemoveGroup is returned when RemoveGroup fails + // ErrRemoveGroup is returned when RemoveGroup fails. ErrRemoveGroup = errors.New("remove group") - // ErrMoveGroup is returned when MoveGroup fails + // ErrMoveGroup is returned when MoveGroup fails. ErrMoveGroup = errors.New("move group") ) -// Validate validates GetGroupRequest +// Validate validates GetGroupRequest. func (r GetGroupRequest) Validate() error { - return validation.Errors{ - "groupID": validation.Validate(r.GroupID, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "GroupID": validation.Validate(r.GroupID, validation.Required), + }) } -// Validate validates GroupRequest +// Validate validates GroupRequest. func (r GroupRequest) Validate() error { return validation.Errors{ - "groupID": validation.Validate(r.GroupID, validation.Required), - "groupName": validation.Validate(r.GroupName, validation.Required), + "GroupID": validation.Validate(r.GroupID, validation.Required), + "GroupName": validation.Validate(r.GroupName, validation.Required), }.Filter() } -// Validate validates MoveGroupRequest +// Validate validates MoveGroupRequest. func (r MoveGroupRequest) Validate() error { - return validation.Errors{ - "destinationGroupID": validation.Validate(r.DestinationGroupID, validation.Required), - "sourceGroupID": validation.Validate(r.SourceGroupID, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DestinationGroupID": validation.Validate(r.DestinationGroupID, validation.Required), + "SourceGroupID": validation.Validate(r.SourceGroupID, validation.Required), + }) } -// Validate validates ListAffectedUsersRequest +// Validate validates ListAffectedUsersRequest. func (r ListAffectedUsersRequest) Validate() error { - return validation.Errors{ - "destinationGroupID": validation.Validate(r.DestinationGroupID, validation.Required), - "sourceGroupID": validation.Validate(r.SourceGroupID, validation.Required), - "userType": validation.Validate(r.UserType, validation.In(LostAccessUsers, GainAccessUsers)), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "DestinationGroupID": validation.Validate(r.DestinationGroupID, validation.Required), + "SourceGroupID": validation.Validate(r.SourceGroupID, validation.Required), + "UserType": validation.Validate(r.UserType, validation.In(LostAccessUsers, GainAccessUsers)), + }) } -// Validate validates RemoveGroupRequest +// Validate validates RemoveGroupRequest. func (r RemoveGroupRequest) Validate() error { - return validation.Errors{ - "groupID": validation.Validate(r.GroupID, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "GroupID": validation.Validate(r.GroupID, validation.Required), + }) } func (i *iam) CreateGroup(ctx context.Context, params GroupRequest) (*Group, error) { @@ -187,12 +151,12 @@ func (i *iam) CreateGroup(ctx context.Context, params GroupRequest) (*Group, err return nil, fmt.Errorf("%s: %w:\n%s", ErrCreateGroup, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/groups/%d", params.GroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/groups/%d", params.GroupID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateGroup, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateGroup, err) } @@ -218,16 +182,16 @@ func (i *iam) GetGroup(ctx context.Context, params GetGroupRequest) (*Group, err return nil, fmt.Errorf("%s: %w:\n%s", ErrGetGroup, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/groups/%d", params.GroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/groups/%d", params.GroupID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetGroup, err) } - q := u.Query() + q := uri.Query() q.Add("actions", strconv.FormatBool(params.Actions)) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetGroup, err) } @@ -253,18 +217,18 @@ func (i *iam) ListAffectedUsers(ctx context.Context, params ListAffectedUsersReq return nil, fmt.Errorf("%s: %w:\n%s", ErrListAffectedUsers, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/groups/move/%d/%d/affected-users", params.SourceGroupID, params.DestinationGroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/groups/move/%d/%d/affected-users", params.SourceGroupID, params.DestinationGroupID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAffectedUsers, err) } if params.UserType != "" { - q := u.Query() + q := uri.Query() q.Add("userType", params.UserType) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAffectedUsers, err) } @@ -286,16 +250,16 @@ func (i *iam) ListGroups(ctx context.Context, params ListGroupsRequest) ([]Group logger := i.Log(ctx) logger.Debug("ListGroups") - u, err := url.Parse("/identity-management/v2/user-admin/groups") + uri, err := url.Parse("/identity-management/v3/user-admin/groups") if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListGroups, err) } - q := u.Query() + q := uri.Query() q.Add("actions", strconv.FormatBool(params.Actions)) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListGroups, err) } @@ -321,12 +285,12 @@ func (i *iam) RemoveGroup(ctx context.Context, params RemoveGroupRequest) error return fmt.Errorf("%s: %w:\n%s", ErrRemoveGroup, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/groups/%d", params.GroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/groups/%d", params.GroupID)) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrRemoveGroup, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrRemoveGroup, err) } @@ -351,12 +315,12 @@ func (i *iam) UpdateGroupName(ctx context.Context, params GroupRequest) (*Group, return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateGroupName, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/groups/%d", params.GroupID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/groups/%d", params.GroupID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateGroupName, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateGroupName, err) } @@ -382,12 +346,12 @@ func (i *iam) MoveGroup(ctx context.Context, params MoveGroupRequest) error { return fmt.Errorf("%s: %w:\n%s", ErrMoveGroup, ErrStructValidation, err) } - u, err := url.Parse("/identity-management/v2/user-admin/groups/move") + uri, err := url.Parse("/identity-management/v3/user-admin/groups/move") if err != nil { return fmt.Errorf("%w: failed to parse url: %s", ErrMoveGroup, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrMoveGroup, err) } diff --git a/pkg/iam/groups_test.go b/pkg/iam/groups_test.go index 20435775..e118b55a 100644 --- a/pkg/iam/groups_test.go +++ b/pkg/iam/groups_test.go @@ -3,16 +3,17 @@ package iam import ( "context" "errors" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" "github.com/stretchr/testify/require" "github.com/tj/assert" ) -func TestCreateGroup(t *testing.T) { +func TestIAM_CreateGroup(t *testing.T) { tests := map[string]struct { params GroupRequest responseStatus int @@ -38,15 +39,15 @@ func TestCreateGroup(t *testing.T) { "modifiedDate": "2012-04-28T00:00:00.000Z", "modifiedBy": "johndoe" }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", expectedRequestBody: `{"groupName":"Test Group"}`, expectedResponse: &Group{ GroupID: 98765, GroupName: "Test Group", ParentGroupID: 12345, - CreatedDate: "2012-04-28T00:00:00.000Z", + CreatedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), CreatedBy: "johndoe", - ModifiedDate: "2012-04-28T00:00:00.000Z", + ModifiedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), ModifiedBy: "johndoe", }, }, @@ -63,7 +64,7 @@ func TestCreateGroup(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -85,34 +86,34 @@ func TestCreateGroup(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) - if len(test.expectedRequestBody) > 0 { - body, err := ioutil.ReadAll(r.Body) + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.Equal(t, test.expectedRequestBody, string(body)) + assert.Equal(t, tc.expectedRequestBody, string(body)) } })) client := mockAPIClient(t, mockServer) - result, err := client.CreateGroup(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + result, err := client.CreateGroup(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } -func TestMoveGroup(t *testing.T) { +func TestIAM_MoveGroup(t *testing.T) { tests := map[string]struct { params MoveGroupRequest expectedRequestBody string @@ -137,22 +138,22 @@ func TestMoveGroup(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "/identity-management/v2/user-admin/groups/move", r.URL.String()) + assert.Equal(t, "/identity-management/v3/user-admin/groups/move", r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.Equal(t, test.expectedRequestBody, string(body)) - w.WriteHeader(test.responseStatus) - _, err = w.Write([]byte(test.responseBody)) + assert.Equal(t, tc.expectedRequestBody, string(body)) + w.WriteHeader(tc.responseStatus) + _, err = w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.MoveGroup(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + err := client.MoveGroup(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) return } require.NoError(t, err) @@ -160,7 +161,7 @@ func TestMoveGroup(t *testing.T) { } } -func TestGetGroup(t *testing.T) { +func TestIAM_GetGroup(t *testing.T) { tests := map[string]struct { params GetGroupRequest responseStatus int @@ -188,13 +189,13 @@ func TestGetGroup(t *testing.T) { "delete": true } }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345?actions=true", + expectedPath: "/identity-management/v3/user-admin/groups/12345?actions=true", expectedResponse: &Group{ GroupID: 12345, GroupName: "Top Level group", - CreatedDate: "2012-04-28T00:00:00.000Z", + CreatedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), CreatedBy: "johndoe", - ModifiedDate: "2012-04-28T00:00:00.000Z", + ModifiedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), ModifiedBy: "johndoe", Actions: &GroupActions{ Edit: true, @@ -216,13 +217,13 @@ func TestGetGroup(t *testing.T) { "modifiedDate": "2012-04-28T00:00:00.000Z", "modifiedBy": "johndoe" }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345?actions=false", + expectedPath: "/identity-management/v3/user-admin/groups/12345?actions=false", expectedResponse: &Group{ GroupID: 12345, GroupName: "Top Level group", - CreatedDate: "2012-04-28T00:00:00.000Z", + CreatedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), CreatedBy: "johndoe", - ModifiedDate: "2012-04-28T00:00:00.000Z", + ModifiedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), ModifiedBy: "johndoe", }, }, @@ -238,7 +239,7 @@ func TestGetGroup(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345?actions=false", + expectedPath: "/identity-management/v3/user-admin/groups/12345?actions=false", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -254,28 +255,28 @@ func TestGetGroup(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetGroup(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + result, err := client.GetGroup(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } -func TestListAffectedUsers(t *testing.T) { +func TestIAM_ListAffectedUsers(t *testing.T) { tests := map[string]struct { params ListAffectedUsersRequest responseStatus int @@ -303,7 +304,7 @@ func TestListAffectedUsers(t *testing.T) { "lastLoginDate": "2022-02-22T17:06:50.000Z" } ]`, - expectedPath: "/identity-management/v2/user-admin/groups/move/12344/12345/affected-users?userType=gainAccess", + expectedPath: "/identity-management/v3/user-admin/groups/move/12344/12345/affected-users?userType=gainAccess", expectedResponse: []GroupUser{ { IdentityID: "test-identity", @@ -312,7 +313,7 @@ func TestListAffectedUsers(t *testing.T) { AccountID: "test-account", Email: "john.doe@mycompany.com", UserName: "john.doe@mycompany.com", - LastLoginDate: "2022-02-22T17:06:50.000Z", + LastLoginDate: test.NewTimeFromString(t, "2022-02-22T17:06:50.000Z"), }, }, }, @@ -330,7 +331,7 @@ func TestListAffectedUsers(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/groups/move/12344/12345/affected-users?userType=gainAccess", + expectedPath: "/identity-management/v3/user-admin/groups/move/12344/12345/affected-users?userType=gainAccess", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -362,28 +363,28 @@ func TestListAffectedUsers(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.ListAffectedUsers(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + result, err := client.ListAffectedUsers(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } -func TestListGroups(t *testing.T) { +func TestIAM_ListGroups(t *testing.T) { tests := map[string]struct { params ListGroupsRequest responseStatus int @@ -412,14 +413,14 @@ func TestListGroups(t *testing.T) { } } ]`, - expectedPath: "/identity-management/v2/user-admin/groups?actions=true", + expectedPath: "/identity-management/v3/user-admin/groups?actions=true", expectedResponse: []Group{ { GroupID: 12345, GroupName: "Top Level group", - CreatedDate: "2012-04-28T00:00:00.000Z", + CreatedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), CreatedBy: "johndoe", - ModifiedDate: "2012-04-28T00:00:00.000Z", + ModifiedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), ModifiedBy: "johndoe", Actions: &GroupActions{ Edit: true, @@ -444,14 +445,14 @@ func TestListGroups(t *testing.T) { "modifiedBy": "johndoe" } ]`, - expectedPath: "/identity-management/v2/user-admin/groups?actions=false", + expectedPath: "/identity-management/v3/user-admin/groups?actions=false", expectedResponse: []Group{ { GroupID: 12345, GroupName: "Top Level group", - CreatedDate: "2012-04-28T00:00:00.000Z", + CreatedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), CreatedBy: "johndoe", - ModifiedDate: "2012-04-28T00:00:00.000Z", + ModifiedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), ModifiedBy: "johndoe", }, }, @@ -468,7 +469,7 @@ func TestListGroups(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/groups?actions=true", + expectedPath: "/identity-management/v3/user-admin/groups?actions=true", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -481,28 +482,28 @@ func TestListGroups(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.ListGroups(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.ListGroups(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } -func TestRemoveGroup(t *testing.T) { +func TestIAM_RemoveGroup(t *testing.T) { tests := map[string]struct { params RemoveGroupRequest responseStatus int @@ -515,7 +516,7 @@ func TestRemoveGroup(t *testing.T) { GroupID: 12345, }, responseStatus: http.StatusNoContent, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", }, "500 internal server error": { params: RemoveGroupRequest{ @@ -529,7 +530,7 @@ func TestRemoveGroup(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -550,7 +551,7 @@ func TestRemoveGroup(t *testing.T) { "title": "Forbidden", "type": "/useradmin-api/error-types/1001" }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", withError: &Error{ Instance: "", StatusCode: http.StatusForbidden, @@ -566,19 +567,19 @@ func TestRemoveGroup(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodDelete, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.RemoveGroup(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + err := client.RemoveGroup(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) return } require.NoError(t, err) @@ -586,7 +587,7 @@ func TestRemoveGroup(t *testing.T) { } } -func TestUpdateGroupName(t *testing.T) { +func TestIAM_UpdateGroupName(t *testing.T) { tests := map[string]struct { params GroupRequest responseStatus int @@ -612,15 +613,15 @@ func TestUpdateGroupName(t *testing.T) { "modifiedDate": "2012-04-28T00:00:00.000Z", "modifiedBy": "johndoe" }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", expectedRequestBody: `{"groupName":"New Group Name"}`, expectedResponse: &Group{ GroupID: 12345, GroupName: "New Group Name", ParentGroupID: 12344, - CreatedDate: "2012-04-28T00:00:00.000Z", + CreatedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), CreatedBy: "johndoe", - ModifiedDate: "2012-04-28T00:00:00.000Z", + ModifiedDate: test.NewTimeFromString(t, "2012-04-28T00:00:00.000Z"), ModifiedBy: "johndoe", }, }, @@ -637,7 +638,7 @@ func TestUpdateGroupName(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/groups/12345", + expectedPath: "/identity-management/v3/user-admin/groups/12345", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -659,29 +660,29 @@ func TestUpdateGroupName(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) - if len(test.expectedRequestBody) > 0 { - body, err := ioutil.ReadAll(r.Body) + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.Equal(t, test.expectedRequestBody, string(body)) + assert.Equal(t, tc.expectedRequestBody, string(body)) } })) client := mockAPIClient(t, mockServer) - result, err := client.UpdateGroupName(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + result, err := client.UpdateGroupName(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } diff --git a/pkg/iam/helper.go b/pkg/iam/helper.go new file mode 100644 index 00000000..573694e7 --- /dev/null +++ b/pkg/iam/helper.go @@ -0,0 +1,288 @@ +package iam + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // ListAllowedCPCodesRequest contains the request parameters for the ListAllowedCPCodes endpoint. + ListAllowedCPCodesRequest struct { + UserName string + Body ListAllowedCPCodesRequestBody + } + + // ListAllowedAPIsRequest contains the request parameters for the ListAllowedAPIs endpoint. + ListAllowedAPIsRequest struct { + UserName string + ClientType ClientType + AllowAccountSwitch bool + } + + // ListAccessibleGroupsRequest contains the request parameter for the ListAccessibleGroups endpoint. + ListAccessibleGroupsRequest struct { + UserName string + } + + // ListAllowedCPCodesRequestBody contains the filtering parameters for the ListAllowedCPCodes endpoint. + ListAllowedCPCodesRequestBody struct { + ClientType ClientType `json:"clientType"` + Groups []AllowedCPCodesGroup `json:"groups"` + } + + // AllowedCPCodesGroup contains the group parameters for the ListAllowedCPCodes endpoint. + AllowedCPCodesGroup struct { + GroupID int64 `json:"groupId,omitempty"` + RoleID int64 `json:"roleId,omitempty"` + GroupName string `json:"groupName,omitempty"` + IsBlocked bool `json:"isBlocked,omitempty"` + ParentGroupID int64 `json:"parentGroupId,omitempty"` + RoleDescription string `json:"roleDescription,omitempty"` + RoleName string `json:"roleName,omitempty"` + SubGroups []AllowedCPCodesGroup `json:"subGroups,omitempty"` + } + + // ListAllowedCPCodesResponse contains response for the ListAllowedCPCodes endpoint. + ListAllowedCPCodesResponse []ListAllowedCPCodesResponseItem + + // ListAllowedCPCodesResponseItem contains single item of the response from the ListAllowedCPCodes endpoint. + ListAllowedCPCodesResponseItem struct { + Name string `json:"name"` + Value int `json:"value"` + } + + // ListAuthorizedUsersResponse contains the response from the ListAuthorizedUsers endpoint. + ListAuthorizedUsersResponse []AuthorizedUser + + // AuthorizedUser contains the details about the authorized user. + AuthorizedUser struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Username string `json:"username"` + Email string `json:"email"` + UIIdentityID string `json:"uiIdentityId"` + } + + // ListAccessibleGroupsResponse contains the response from the ListAccessibleGroups endpoint. + ListAccessibleGroupsResponse []AccessibleGroup + + // AccessibleGroup contains the details about accessible group. + AccessibleGroup struct { + GroupID int64 `json:"groupId"` + RoleID int64 `json:"roleId"` + GroupName string `json:"groupName"` + RoleName string `json:"roleName"` + IsBlocked bool `json:"isBlocked"` + RoleDescription string `json:"roleDescription"` + SubGroups []AccessibleSubGroup `json:"subGroups"` + } + + // AccessibleSubGroup contains the details about subgroup. + AccessibleSubGroup struct { + GroupID int64 `json:"groupId"` + GroupName string `json:"groupName"` + ParentGroupID int64 `json:"parentGroupId"` + SubGroups []AccessibleSubGroup `json:"subGroups"` + } + + // ListAllowedAPIsResponse contains the response from the ListAllowedAPIs endpoint. + ListAllowedAPIsResponse []AllowedAPI + + // AllowedAPI contains the details about the API. + AllowedAPI struct { + AccessLevels []AccessLevel `json:"accessLevels"` + APIID int64 `json:"apiId"` + APIName string `json:"apiName"` + Description string `json:"description"` + DocumentationURL string `json:"documentationUrl"` + Endpoint string `json:"endpoint"` + HasAccess bool `json:"hasAccess"` + ServiceProviderID int64 `json:"serviceProviderId"` + } + + // ClientType represents the type of the client. + ClientType string +) + +const ( + // UserClientType is the `USER_CLIENT` client type. + UserClientType ClientType = "USER_CLIENT" + // ServiceAccountClientType is the `SERVICE_ACCOUNT` client type. + ServiceAccountClientType ClientType = "SERVICE_ACCOUNT" + // ClientClientType is the `CLIENT` client type. + ClientClientType ClientType = "CLIENT" +) + +var ( + // ErrListAllowedCPCodes is returned when ListAllowedCPCodes fails. + ErrListAllowedCPCodes = errors.New("list allowed CP codes") + // ErrListAuthorizedUsers is returned when ListAuthorizedUsers fails. + ErrListAuthorizedUsers = errors.New("list authorized users") + // ErrListAllowedAPIs is returned when ListAllowedAPIs fails. + ErrListAllowedAPIs = errors.New("list allowed APIs") + // ErrAccessibleGroups is returned when ListAccessibleGroups fails. + ErrAccessibleGroups = errors.New("list accessible groups") +) + +// Validate validates ClientType. +func (c ClientType) Validate() error { + return validation.In(ClientClientType, ServiceAccountClientType, UserClientType). + Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s' or '%s'", + c, ClientClientType, ServiceAccountClientType, UserClientType)). + Validate(c) +} + +// Validate validates ListAllowedCPCodesRequest. +func (r ListAllowedCPCodesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "UserName": validation.Validate(r.UserName, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), + }) +} + +// Validate validates ListAllowedCPCodesRequestBody. +func (r ListAllowedCPCodesRequestBody) Validate() error { + return validation.Errors{ + "ClientType": validation.Validate(r.ClientType, validation.Required, validation.In(ClientClientType, UserClientType, ServiceAccountClientType).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: 'CLIENT' or 'USER_CLIENT' or 'SERVICE_ACCOUNT'", r.ClientType))), + "Groups": validation.Validate(r.Groups, validation.Required.When(r.ClientType == ServiceAccountClientType)), + }.Filter() +} + +// Validate validates ListAllowedAPIsRequest. +func (r ListAllowedAPIsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "UserName": validation.Validate(r.UserName, validation.Required), + "ClientType": validation.Validate(r.ClientType, validation.In(ClientClientType, UserClientType, ServiceAccountClientType).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: 'CLIENT' or 'USER_CLIENT' or 'SERVICE_ACCOUNT'", r.ClientType))), + }) +} + +// Validate validates ListAccessibleGroupsRequest. +func (r ListAccessibleGroupsRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "UserName": validation.Validate(r.UserName, validation.Required), + }) +} + +func (i *iam) ListAllowedCPCodes(ctx context.Context, params ListAllowedCPCodesRequest) (ListAllowedCPCodesResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListAllowedCPCodes") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrListAllowedCPCodes, ErrStructValidation, err) + } + + uri := fmt.Sprintf("/identity-management/v3/users/%s/allowed-cpcodes", params.UserName) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAllowedCPCodes, err) + } + + var result ListAllowedCPCodesResponse + resp, err := i.Exec(req, &result, params.Body) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListAllowedCPCodes, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListAllowedCPCodes, i.Error(resp)) + } + + return result, nil +} + +func (i *iam) ListAuthorizedUsers(ctx context.Context) (ListAuthorizedUsersResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListAuthorizedUsers") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/identity-management/v3/users", nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAuthorizedUsers, err) + } + + var result ListAuthorizedUsersResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListAuthorizedUsers, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListAuthorizedUsers, i.Error(resp)) + } + + return result, nil +} + +func (i *iam) ListAllowedAPIs(ctx context.Context, params ListAllowedAPIsRequest) (ListAllowedAPIsResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListAllowedAPIs") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrListAllowedAPIs, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/users/%s/allowed-apis", params.UserName)) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAllowedAPIs, err) + } + + q := uri.Query() + if params.ClientType != "" { + q.Add("clientType", string(params.ClientType)) + + } + q.Add("allowAccountSwitch", strconv.FormatBool(params.AllowAccountSwitch)) + uri.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAllowedAPIs, err) + } + + var result ListAllowedAPIsResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListAllowedAPIs, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListAllowedAPIs, i.Error(resp)) + } + + return result, nil +} + +func (i *iam) ListAccessibleGroups(ctx context.Context, params ListAccessibleGroupsRequest) (ListAccessibleGroupsResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListAccessibleGroups") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrAccessibleGroups, ErrStructValidation, err) + } + + uri := fmt.Sprintf("/identity-management/v3/users/%s/group-access", params.UserName) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrAccessibleGroups, err) + } + + var result ListAccessibleGroupsResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrAccessibleGroups, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrAccessibleGroups, i.Error(resp)) + } + + return result, nil +} diff --git a/pkg/iam/helper_test.go b/pkg/iam/helper_test.go new file mode 100644 index 00000000..4384e6b7 --- /dev/null +++ b/pkg/iam/helper_test.go @@ -0,0 +1,603 @@ +package iam + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tj/assert" +) + +func TestIAM_ListAllowedCPCodes(t *testing.T) { + tests := map[string]struct { + params ListAllowedCPCodesRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse ListAllowedCPCodesResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: ListAllowedCPCodesRequest{ + UserName: "jsmith", + Body: ListAllowedCPCodesRequestBody{ + ClientType: ClientClientType, + }, + }, + responseStatus: http.StatusOK, + responseBody: `[ + { + "name": "Stream Analyzer (36915)", + "value": 36915 + }, + { + "name": "plopessa-uvod-ns (373118)", + "value": 373118 + }, + { + "name": "ArunNS (866797)", + "value": 866797 + }, + { + "name": "1234 (933076)", + "value": 933076 + } +]`, + expectedPath: "/identity-management/v3/users/jsmith/allowed-cpcodes", + expectedResponse: ListAllowedCPCodesResponse{ + { + Name: "Stream Analyzer (36915)", + Value: 36915, + }, + { + Name: "plopessa-uvod-ns (373118)", + Value: 373118, + }, + { + Name: "ArunNS (866797)", + Value: 866797, + }, + { + Name: "1234 (933076)", + Value: 933076, + }, + }, + }, + "200 OK with groups": { + params: ListAllowedCPCodesRequest{ + UserName: "jsmith", + Body: ListAllowedCPCodesRequestBody{ + ClientType: ServiceAccountClientType, + Groups: []AllowedCPCodesGroup{ + { + GroupID: 1, + }, + }, + }, + }, + responseStatus: http.StatusOK, + responseBody: `[ + { + "name": "Stream Analyzer (36915)", + "value": 36915 + }, + { + "name": "plopessa-uvod-ns (373118)", + "value": 373118 + }, + { + "name": "ArunNS (866797)", + "value": 866797 + }, + { + "name": "1234 (933076)", + "value": 933076 + } +]`, + expectedPath: "/identity-management/v3/users/jsmith/allowed-cpcodes", + expectedResponse: ListAllowedCPCodesResponse{ + { + Name: "Stream Analyzer (36915)", + Value: 36915, + }, + { + Name: "plopessa-uvod-ns (373118)", + Value: 373118, + }, + { + Name: "ArunNS (866797)", + Value: 866797, + }, + { + Name: "1234 (933076)", + Value: 933076, + }, + }, + }, + "500 internal server error": { + params: ListAllowedCPCodesRequest{ + UserName: "jsmith", + Body: ListAllowedCPCodesRequestBody{ + ClientType: ClientClientType, + }, + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + expectedPath: "/identity-management/v3/users/jsmith/allowed-cpcodes", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "missing user name and client type": { + params: ListAllowedCPCodesRequest{}, + withError: func(t *testing.T, err error) { + want := ErrStructValidation + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + assert.Contains(t, err.Error(), "list allowed CP codes: struct validation:\nClientType: cannot be blank\nUserName: cannot be blank") + }, + }, + "group is required for client type SERVICE_ACCOUNT": { + params: ListAllowedCPCodesRequest{ + UserName: "jsmith", + Body: ListAllowedCPCodesRequestBody{ + ClientType: ServiceAccountClientType, + }, + }, + withError: func(t *testing.T, err error) { + want := ErrStructValidation + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + assert.Contains(t, err.Error(), "list allowed CP codes: struct validation:\nGroups: cannot be blank") + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.ListAllowedCPCodes(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_ListAuthorizedUsers(t *testing.T) { + tests := map[string]struct { + responseStatus int + responseBody string + expectedPath string + expectedResponse ListAuthorizedUsersResponse + withError func(*testing.T, error) + }{ + "200 OK": { + responseStatus: http.StatusOK, + responseBody: `[ + { + "username": "test.example.user", + "firstName": "Edd", + "lastName": "Example", + "email": "test_example@akamai.com", + "uiIdentityId": "X-YZ-1111111" + }, + { + "username": "test.example.user2", + "firstName": "Fred", + "lastName": "Example2", + "email": "test_example2@akamai.com", + "uiIdentityId": "X-YZ-2222222" + }, + { + "username": "test.example.user3", + "firstName": "Ted", + "lastName": "Example3", + "email": "test_example3@akamai.com", + "uiIdentityId": "X-YZ-3333333" + } +]`, + expectedPath: "/identity-management/v3/users", + expectedResponse: ListAuthorizedUsersResponse{ + { + Username: "test.example.user", + FirstName: "Edd", + LastName: "Example", + Email: "test_example@akamai.com", + UIIdentityID: "X-YZ-1111111", + }, + { + Username: "test.example.user2", + FirstName: "Fred", + LastName: "Example2", + Email: "test_example2@akamai.com", + UIIdentityID: "X-YZ-2222222", + }, + { + Username: "test.example.user3", + FirstName: "Ted", + LastName: "Example3", + Email: "test_example3@akamai.com", + UIIdentityID: "X-YZ-3333333", + }, + }, + }, + "500 internal server error": { + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + expectedPath: "/identity-management/v3/users", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.ListAuthorizedUsers(context.Background()) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_ListAllowedAPIs(t *testing.T) { + tests := map[string]struct { + params ListAllowedAPIsRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse ListAllowedAPIsResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: ListAllowedAPIsRequest{ + UserName: "jsmith", + }, + responseStatus: http.StatusOK, + responseBody: `[ + { + "apiId": 1111, + "serviceProviderId": 1, + "apiName": "Test API Name", + "description": "Test API Name", + "endPoint": "/test-api-name/", + "documentationUrl": "https://example.akamai.com/", + "accessLevels": [ + "READ-WRITE", + "READ-ONLY" + ], + "hasAccess": false + }, + { + "apiId": 2222, + "serviceProviderId": 1, + "apiName": "Example API Name", + "description": "Example API Name", + "endPoint": "/example-api-name/", + "documentationUrl": "https://example2.akamai.com/", + "accessLevels": [ + "READ-WRITE", + "READ-ONLY" + ], + "hasAccess": false + }, + { + "apiId": 3333, + "serviceProviderId": 1, + "apiName": "Best API Name", + "description": "Best API Name", + "endPoint": "/best-api-name/", + "documentationUrl": "https://example3.akamai.com/", + "accessLevels": [ + "READ-WRITE", + "READ-ONLY" + ], + "hasAccess": false + } +]`, + expectedPath: "/identity-management/v3/users/jsmith/allowed-apis?allowAccountSwitch=false", + expectedResponse: ListAllowedAPIsResponse{ + { + APIID: 1111, + ServiceProviderID: 1, + APIName: "Test API Name", + Description: "Test API Name", + Endpoint: "/test-api-name/", + DocumentationURL: "https://example.akamai.com/", + AccessLevels: []AccessLevel{ReadWriteLevel, ReadOnlyLevel}, + HasAccess: false, + }, + { + APIID: 2222, + ServiceProviderID: 1, + APIName: "Example API Name", + Description: "Example API Name", + Endpoint: "/example-api-name/", + DocumentationURL: "https://example2.akamai.com/", + AccessLevels: []AccessLevel{ReadWriteLevel, ReadOnlyLevel}, + HasAccess: false, + }, + { + APIID: 3333, + ServiceProviderID: 1, + APIName: "Best API Name", + Description: "Best API Name", + Endpoint: "/best-api-name/", + DocumentationURL: "https://example3.akamai.com/", + AccessLevels: []AccessLevel{ReadWriteLevel, ReadOnlyLevel}, + HasAccess: false, + }, + }, + }, + "200 OK with query params": { + params: ListAllowedAPIsRequest{ + UserName: "jsmith", + ClientType: UserClientType, + AllowAccountSwitch: true, + }, + responseStatus: http.StatusOK, + responseBody: `[ + { + "apiId": 1111, + "serviceProviderId": 1, + "apiName": "Test API Name", + "description": "Test API Name", + "endPoint": "/test-api-name/", + "documentationUrl": "https://example.akamai.com/", + "accessLevels": [ + "READ-WRITE", + "READ-ONLY" + ], + "hasAccess": false + } +]`, + expectedPath: "/identity-management/v3/users/jsmith/allowed-apis?allowAccountSwitch=true&clientType=USER_CLIENT", + expectedResponse: ListAllowedAPIsResponse{ + { + APIID: 1111, + ServiceProviderID: 1, + APIName: "Test API Name", + Description: "Test API Name", + Endpoint: "/test-api-name/", + DocumentationURL: "https://example.akamai.com/", + AccessLevels: []AccessLevel{ReadWriteLevel, ReadOnlyLevel}, + HasAccess: false, + }, + }, + }, + "500 internal server error": { + params: ListAllowedAPIsRequest{ + UserName: "jsmith", + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + expectedPath: "/identity-management/v3/users/jsmith/allowed-apis?allowAccountSwitch=false", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "missing user name": { + params: ListAllowedAPIsRequest{}, + withError: func(t *testing.T, err error) { + want := ErrStructValidation + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + assert.Contains(t, err.Error(), "list allowed APIs: struct validation:\nUserName: cannot be blank") + }, + }, + "wrong client type": { + params: ListAllowedAPIsRequest{ + UserName: "jsmith", + ClientType: "Test", + }, + withError: func(t *testing.T, err error) { + want := ErrStructValidation + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + assert.Contains(t, err.Error(), "list allowed APIs: struct validation:\nClientType: value 'Test' is invalid. Must be one of: 'CLIENT' or 'USER_CLIENT' or 'SERVICE_ACCOUNT'") + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.ListAllowedAPIs(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_AccessibleGroups(t *testing.T) { + tests := map[string]struct { + params ListAccessibleGroupsRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse ListAccessibleGroupsResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: ListAccessibleGroupsRequest{ + UserName: "jsmith", + }, + responseStatus: http.StatusOK, + responseBody: `[ + { + "groupId": 1111, + "groupName": "TestGroupName", + "roleId": 123123, + "roleName": "Test Role Name", + "roleDescription": "Test Role Description", + "isBlocked": false, + "subGroups": [ + { + "groupId": 3333, + "groupName": "TestSubGroupName", + "parentGroupId": 1111, + "subGroups": [] + } + ] + }, + { + "groupId": 2222, + "groupName": "TestGroupName2", + "roleId": 321321, + "roleName": "Test Role Name 2", + "roleDescription": "Test Role Description 2", + "isBlocked": false, + "subGroups": [] + } +]`, + expectedPath: "/identity-management/v3/users/jsmith/group-access", + expectedResponse: ListAccessibleGroupsResponse{ + { + GroupID: 1111, + RoleID: 123123, + GroupName: "TestGroupName", + RoleName: "Test Role Name", + IsBlocked: false, + RoleDescription: "Test Role Description", + SubGroups: []AccessibleSubGroup{ + { + GroupID: 3333, + GroupName: "TestSubGroupName", + ParentGroupID: 1111, + SubGroups: []AccessibleSubGroup{}, + }, + }, + }, + { + GroupID: 2222, + RoleID: 321321, + GroupName: "TestGroupName2", + RoleName: "Test Role Name 2", + IsBlocked: false, + RoleDescription: "Test Role Description 2", + SubGroups: []AccessibleSubGroup{}, + }, + }, + }, + "500 internal server error": { + params: ListAccessibleGroupsRequest{ + UserName: "jsmith", + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + expectedPath: "/identity-management/v3/users/jsmith/group-access", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "missing user name": { + params: ListAccessibleGroupsRequest{}, + withError: func(t *testing.T, err error) { + want := ErrStructValidation + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + assert.Contains(t, err.Error(), "list accessible groups: struct validation:\nUserName: cannot be blank") + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.ListAccessibleGroups(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} diff --git a/pkg/iam/iam.go b/pkg/iam/iam.go index e3476951..9d31b075 100644 --- a/pkg/iam/iam.go +++ b/pkg/iam/iam.go @@ -2,9 +2,10 @@ package iam import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,28 +16,404 @@ var ( type ( // IAM is the IAM api interface IAM interface { - BlockedProperties - Groups - Properties - Roles - Support - UserLock - UserPassword - Users + + // API Clients + + // LockAPIClient locks an API client based on `ClientID` parameter. If `ClientID` is not provided, it locks your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-lock-api-client, https://techdocs.akamai.com/iam-api/reference/put-lock-api-client-self + LockAPIClient(ctx context.Context, params LockAPIClientRequest) (*LockAPIClientResponse, error) + + // UnlockAPIClient unlocks an API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-unlock-api-client + UnlockAPIClient(ctx context.Context, params UnlockAPIClientRequest) (*UnlockAPIClientResponse, error) + + // ListAPIClients lists API clients an administrator can manage. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-api-clients + ListAPIClients(ctx context.Context, params ListAPIClientsRequest) (ListAPIClientsResponse, error) + + // GetAPIClient provides details about an API client. If `ClientID` is not provided, it returns details about your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-api-client and https://techdocs.akamai.com/iam-api/reference/get-api-client-self + GetAPIClient(ctx context.Context, params GetAPIClientRequest) (*GetAPIClientResponse, error) + + // CreateAPIClient creates a new API client. Optionally, it can automatically assign a credential for the client when creating it. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-api-clients + CreateAPIClient(ctx context.Context, params CreateAPIClientRequest) (*CreateAPIClientResponse, error) + + // UpdateAPIClient updates an API client. If `ClientID` is not provided, it updates your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-api-clients and https://techdocs.akamai.com/iam-api/reference/put-api-clients-self + UpdateAPIClient(ctx context.Context, params UpdateAPIClientRequest) (*UpdateAPIClientResponse, error) + + // DeleteAPIClient permanently deletes the API client, breaking any API connections with the client. + // If `ClientID` is not provided, it deletes your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/delete-api-client and https://techdocs.akamai.com/iam-api/reference/delete-api-client-self + DeleteAPIClient(ctx context.Context, params DeleteAPIClientRequest) error + + // API Client Credentials + + // CreateCredential creates a new credential for the API client. If `ClientID` is not provided, it creates credential for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-self-credentials, https://techdocs.akamai.com/iam-api/reference/post-client-credentials + CreateCredential(context.Context, CreateCredentialRequest) (*CreateCredentialResponse, error) + + // ListCredentials lists credentials for an API client. If `ClientID` is not provided, it lists credentials for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-self-credentials, https://techdocs.akamai.com/iam-api/reference/get-client-credentials + ListCredentials(context.Context, ListCredentialsRequest) (ListCredentialsResponse, error) + + // GetCredential returns details about a specific credential for an API client. If `ClientID` is not provided, it gets credential for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-self-credential, https://techdocs.akamai.com/iam-api/reference/get-client-credential + GetCredential(context.Context, GetCredentialRequest) (*GetCredentialResponse, error) + + // UpdateCredential updates a specific credential for an API client. If `ClientID` is not provided, it updates credential for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-self-credential, https://techdocs.akamai.com/iam-api/reference/put-client-credential + UpdateCredential(context.Context, UpdateCredentialRequest) (*UpdateCredentialResponse, error) + + // DeleteCredential deletes a specific credential from an API client. If `ClientID` is not provided, it deletes credential for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/delete-self-credential, https://techdocs.akamai.com/iam-api/reference/delete-client-credential + DeleteCredential(context.Context, DeleteCredentialRequest) error + + // DeactivateCredential deactivates a specific credential for an API client. If `ClientID` is not provided, it deactivates credential for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-self-credential-deactivate, https://techdocs.akamai.com/iam-api/reference/post-client-credential-deactivate + DeactivateCredential(context.Context, DeactivateCredentialRequest) error + + // DeactivateCredentials deactivates all credentials for a specific API client. If `ClientID` is not provided, it deactivates all credentials for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-self-credentials-deactivate, https://techdocs.akamai.com/iam-api/reference/post-client-credentials-deactivate + DeactivateCredentials(context.Context, DeactivateCredentialsRequest) error + + // Blocked Properties + + // ListBlockedProperties returns all properties a user doesn't have access to in a group. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-blocked-properties + ListBlockedProperties(context.Context, ListBlockedPropertiesRequest) ([]int64, error) + + // UpdateBlockedProperties removes or grants user access to properties. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-blocked-properties + UpdateBlockedProperties(context.Context, UpdateBlockedPropertiesRequest) ([]int64, error) + + // CIDR Blocks + + // ListCIDRBlocks lists all CIDR blocks on selected account's allowlist. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-allowlist + ListCIDRBlocks(context.Context, ListCIDRBlocksRequest) (ListCIDRBlocksResponse, error) + + // CreateCIDRBlock adds CIDR blocks to your account's allowlist. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-allowlist + CreateCIDRBlock(context.Context, CreateCIDRBlockRequest) (*CreateCIDRBlockResponse, error) + + // GetCIDRBlock retrieves a CIDR block's details. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-allowlist-cidrblockid + GetCIDRBlock(context.Context, GetCIDRBlockRequest) (*GetCIDRBlockResponse, error) + + // UpdateCIDRBlock modifies an existing CIDR block. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-allowlist-cidrblockid + UpdateCIDRBlock(context.Context, UpdateCIDRBlockRequest) (*UpdateCIDRBlockResponse, error) + + // DeleteCIDRBlock deletes an existing CIDR block from the IP allowlist. + // + // See: https://techdocs.akamai.com/iam-api/reference/delete-allowlist-cidrblockid + DeleteCIDRBlock(context.Context, DeleteCIDRBlockRequest) error + + // ValidateCIDRBlock checks the format of CIDR block. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-allowlist-validate + ValidateCIDRBlock(context.Context, ValidateCIDRBlockRequest) error + + // Groups + + // CreateGroup creates a new group within a parent group_id specified in the request. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-group + CreateGroup(context.Context, GroupRequest) (*Group, error) + + // GetGroup returns a group's details. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-group + GetGroup(context.Context, GetGroupRequest) (*Group, error) + + // ListAffectedUsers lists users who are affected when a group is moved. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-move-affected-users + ListAffectedUsers(context.Context, ListAffectedUsersRequest) ([]GroupUser, error) + + // ListGroups lists all groups in which you have a scope of admin for the current account and contract type. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-groups + ListGroups(context.Context, ListGroupsRequest) ([]Group, error) + + // RemoveGroup removes a group based on group_id. We can only delete a sub-group, and only if that sub-group doesn't include any users. + // + // See: https://techdocs.akamai.com/iam-api/reference/delete-group + RemoveGroup(context.Context, RemoveGroupRequest) error + + // UpdateGroupName changes the name of the group. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-group + UpdateGroupName(context.Context, GroupRequest) (*Group, error) + + // MoveGroup moves a nested group under another group within the same parent hierarchy. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-groups-move + MoveGroup(context.Context, MoveGroupRequest) error + + // Helpers + + // ListAllowedCPCodes lists available CP codes for a user. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-api-clients-users-allowed-cpcodes + ListAllowedCPCodes(context.Context, ListAllowedCPCodesRequest) (ListAllowedCPCodesResponse, error) + + // ListAuthorizedUsers lists authorized API client users. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-api-clients-users + ListAuthorizedUsers(context.Context) (ListAuthorizedUsersResponse, error) + + // ListAllowedAPIs lists available APIs for a user. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-api-clients-users-allowed-apis + ListAllowedAPIs(context.Context, ListAllowedAPIsRequest) (ListAllowedAPIsResponse, error) + + // ListAccessibleGroups lists groups available to a user. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-api-clients-users-group-access + ListAccessibleGroups(context.Context, ListAccessibleGroupsRequest) (ListAccessibleGroupsResponse, error) + + // IP Allowlist + + // DisableIPAllowlist disables IP allowlist on your account. After you disable IP allowlist, + // users can access Control Center regardless of their IP address or who assigns it. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-allowlist-disable + DisableIPAllowlist(context.Context) error + + // EnableIPAllowlist enables IP allowlist on your account. Before you enable IP allowlist, + // add at least one IP address to allow access to Control Center. + // The allowlist can't be empty with IP allowlist enabled. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-allowlist-enable + EnableIPAllowlist(context.Context) error + + // GetIPAllowlistStatus indicates whether IP allowlist is enabled or disabled on your account. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-allowlist-status + GetIPAllowlistStatus(context.Context) (*GetIPAllowlistStatusResponse, error) + + // Properties + + // ListProperties lists the properties for the current account or other managed accounts using the accountSwitchKey parameter. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-properties + ListProperties(context.Context, ListPropertiesRequest) (ListPropertiesResponse, error) + + // ListUsersForProperty lists users who can access a property. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-property-users + ListUsersForProperty(context.Context, ListUsersForPropertyRequest) (ListUsersForPropertyResponse, error) + + // GetProperty lists a property's details. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-property + GetProperty(context.Context, GetPropertyRequest) (*GetPropertyResponse, error) + + // MoveProperty moves a property from one group to another group. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-property + MoveProperty(context.Context, MovePropertyRequest) error + + // MapPropertyIDToName returns property name for given (IAM) property ID + // Mainly to be used to map (IAM) Property ID to (PAPI) Property ID + // To finish the mapping, please use papi.MapPropertyNameToID + MapPropertyIDToName(context.Context, MapPropertyIDToNameRequest) (*string, error) + + // BlockUsers blocks the users on a property. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-property-users-block + BlockUsers(context.Context, BlockUsersRequest) (*BlockUsersResponse, error) + + // Roles + + // CreateRole creates a custom role. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-role + CreateRole(context.Context, CreateRoleRequest) (*Role, error) + + // GetRole gets details for a specific role. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-role + GetRole(context.Context, GetRoleRequest) (*Role, error) + + // UpdateRole adds or removes permissions from a role and updates other parameters. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-role + UpdateRole(context.Context, UpdateRoleRequest) (*Role, error) + + // DeleteRole deletes a role. This operation is only allowed if the role isn't assigned to any users. + // + // See: https://techdocs.akamai.com/iam-api/reference/delete-role + DeleteRole(context.Context, DeleteRoleRequest) error + + // ListRoles lists roles for the current account and contract type. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-roles + ListRoles(context.Context, ListRolesRequest) ([]Role, error) + + // ListGrantableRoles lists which grantable roles can be included in a new custom role or added to an existing custom role. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-grantable-roles + ListGrantableRoles(context.Context) ([]RoleGrantedRole, error) + + // Support + + // GetPasswordPolicy gets the password policy for the account. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-password-policy + GetPasswordPolicy(ctx context.Context) (*GetPasswordPolicyResponse, error) + + // ListProducts lists products a user can subscribe to and receive notifications for on the account. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-notification-products + ListProducts(context.Context) ([]string, error) + + // ListStates lists U.S. states or Canadian provinces. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-states + ListStates(context.Context, ListStatesRequest) ([]string, error) + + // ListTimeoutPolicies lists all the possible session timeout policies. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-timeout-policies + ListTimeoutPolicies(context.Context) ([]TimeoutPolicy, error) + + // ListAccountSwitchKeys lists account switch keys available for a specific API client. If `ClientID` is not provided, it lists account switch keys available for your API client. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-client-account-switch-keys, https://techdocs.akamai.com/iam-api/reference/get-self-account-switch-keys + ListAccountSwitchKeys(context.Context, ListAccountSwitchKeysRequest) (ListAccountSwitchKeysResponse, error) + + // SupportedContactTypes lists supported contact types. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-contact-types + SupportedContactTypes(context.Context) ([]string, error) + + // SupportedCountries lists supported countries. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-countries + SupportedCountries(context.Context) ([]string, error) + + // SupportedLanguages lists supported languages. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-languages + SupportedLanguages(context.Context) ([]string, error) + + // SupportedTimezones lists supported timezones. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-common-timezones + SupportedTimezones(context.Context) ([]Timezone, error) + + // Users + + // LockUser locks the user. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-ui-identity-lock + LockUser(context.Context, LockUserRequest) error + + // UnlockUser releases the lock on a user's account. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-ui-identity-unlock + UnlockUser(context.Context, UnlockUserRequest) error + + // ResetUserPassword optionally sends a one-time use password to the user. + // If you send the email with the password directly to the user, the response for this operation doesn't include that password. + // If you don't send the password to the user through email, the password is included in the response. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-reset-password + ResetUserPassword(context.Context, ResetUserPasswordRequest) (*ResetUserPasswordResponse, error) + + // SetUserPassword sets a specific password for a user. + // + // See: https://techdocs.akamai.com/iam-api/reference/post-set-password + SetUserPassword(context.Context, SetUserPasswordRequest) error + + // CreateUser creates a user in the account specified in your own API client credentials or clone an existing user's role assignments. + // + // See: https://techdocs.akamai.com/iam-user-admin/reference/post-ui-identity + CreateUser(context.Context, CreateUserRequest) (*User, error) + + // GetUser gets a specific user's profile. + // + // See: https://techdocs.akamai.com/iam-user-admin/reference/get-ui-identity + GetUser(context.Context, GetUserRequest) (*User, error) + + // ListUsers returns a list of users who have access on this account. + // + // See: https://techdocs.akamai.com/iam-api/reference/get-ui-identities + ListUsers(context.Context, ListUsersRequest) ([]UserListItem, error) + + // RemoveUser removes a user identity. + // + // See: https://techdocs.akamai.com/iam-api/reference/delete-ui-identity + RemoveUser(context.Context, RemoveUserRequest) error + + // UpdateUserAuthGrants edits what groups a user has access to, and how the user can interact with the objects in those groups. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-ui-uiidentity-auth-grants + UpdateUserAuthGrants(context.Context, UpdateUserAuthGrantsRequest) ([]AuthGrant, error) + + // UpdateUserInfo updates a user's information. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-ui-identity-basic-info + UpdateUserInfo(context.Context, UpdateUserInfoRequest) (*UserBasicInfo, error) + + // UpdateUserNotifications subscribes or un-subscribes user to product notification emails. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-notifications + UpdateUserNotifications(context.Context, UpdateUserNotificationsRequest) (*UserNotifications, error) + + // UpdateTFA updates a user's two-factor authentication setting and can reset tfa. + // + // See: https://techdocs.akamai.com/iam-user-admin/reference/put-ui-identity-tfa + /** @deprecated */ + UpdateTFA(context.Context, UpdateTFARequest) error + + // UpdateMFA updates a user's profile authentication method. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-user-profile-additional-authentication + UpdateMFA(context.Context, UpdateMFARequest) error + + // ResetMFA resets a user's profile authentication method. + // + // See: https://techdocs.akamai.com/iam-api/reference/put-ui-identity-reset-additional-authentication + ResetMFA(context.Context, ResetMFARequest) error } iam struct { session.Session } - // Option defines a IAM option + // Option defines a IAM option. Option func(*iam) - // ClientFunc is an IAM client new method, this can be used for mocking + // ClientFunc is an IAM client new method, this can be used for mocking. ClientFunc func(sess session.Session, opts ...Option) IAM ) -// Client returns a new IAM Client instance with the specified controller +// Client returns a new IAM Client instance with the specified controller. func Client(sess session.Session, opts ...Option) IAM { p := &iam{ Session: sess, diff --git a/pkg/iam/iam_test.go b/pkg/iam/iam_test.go index 84a3a631..f3301864 100644 --- a/pkg/iam/iam_test.go +++ b/pkg/iam/iam_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/iam/ip_allowlist.go b/pkg/iam/ip_allowlist.go new file mode 100644 index 00000000..264d2dd0 --- /dev/null +++ b/pkg/iam/ip_allowlist.go @@ -0,0 +1,94 @@ +package iam + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +type ( + // GetIPAllowlistStatusResponse contains response from the GetIPAllowlistStatus endpoint. + GetIPAllowlistStatusResponse struct { + Enabled bool `json:"enabled"` + } +) + +var ( + // ErrDisableIPAllowlist is returned when DisableIPAllowlist fails. + ErrDisableIPAllowlist = errors.New("disable ip allowlist") + // ErrEnableIPAllowlist is returned when EnableIPAllowlist fails. + ErrEnableIPAllowlist = errors.New("enable ip allowlist") + // ErrGetIPAllowlistStatus is returned when GetIPAllowlistStatus fails. + ErrGetIPAllowlistStatus = errors.New("get ip allowlist status") +) + +func (i *iam) DisableIPAllowlist(ctx context.Context) error { + logger := i.Log(ctx) + logger.Debug("DisableIPAllowlist") + + uri := "/identity-management/v3/user-admin/ip-acl/allowlist/disable" + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrDisableIPAllowlist, err) + } + + resp, err := i.Exec(req, nil, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrDisableIPAllowlist, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrDisableIPAllowlist, i.Error(resp)) + } + + return nil +} + +func (i *iam) EnableIPAllowlist(ctx context.Context) error { + logger := i.Log(ctx) + logger.Debug("EnableIPAllowlist") + + uri := "/identity-management/v3/user-admin/ip-acl/allowlist/enable" + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrEnableIPAllowlist, err) + } + + resp, err := i.Exec(req, nil, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrEnableIPAllowlist, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrEnableIPAllowlist, i.Error(resp)) + } + + return nil +} + +func (i *iam) GetIPAllowlistStatus(ctx context.Context) (*GetIPAllowlistStatusResponse, error) { + logger := i.Log(ctx) + logger.Debug("GetIPAllowlistStatus") + + uri := "/identity-management/v3/user-admin/ip-acl/allowlist/status" + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetIPAllowlistStatus, err) + } + + var result GetIPAllowlistStatusResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrGetIPAllowlistStatus, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrGetIPAllowlistStatus, i.Error(resp)) + } + + return &result, nil +} diff --git a/pkg/iam/ip_allowlist_test.go b/pkg/iam/ip_allowlist_test.go new file mode 100644 index 00000000..5e08435a --- /dev/null +++ b/pkg/iam/ip_allowlist_test.go @@ -0,0 +1,195 @@ +package iam + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tj/assert" +) + +func TestIAM_DisableIPAllowlist(t *testing.T) { + tests := map[string]struct { + responseStatus int + expectedPath string + responseBody string + withError func(*testing.T, error) + }{ + "204 no content": { + responseStatus: http.StatusNoContent, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/disable", + }, + "500 internal server error": { + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/disable", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + if tc.responseBody != "" { + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + } + })) + client := mockAPIClient(t, mockServer) + err := client.DisableIPAllowlist(context.Background()) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_EnableIPAllowlist(t *testing.T) { + tests := map[string]struct { + responseStatus int + expectedPath string + responseBody string + withError func(*testing.T, error) + }{ + "204 no content": { + responseStatus: http.StatusNoContent, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/enable", + }, + "500 internal server error": { + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 + }`, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/enable", + withError: func(t *testing.T, e error) { + err := Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + w.WriteHeader(tc.responseStatus) + if tc.responseBody != "" { + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + } + })) + client := mockAPIClient(t, mockServer) + err := client.EnableIPAllowlist(context.Background()) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_GetIPAllowlistStatus(t *testing.T) { + tests := map[string]struct { + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetIPAllowlistStatusResponse + withError func(*testing.T, error) + }{ + "200 OK enabled true": { + responseStatus: 200, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/status", + responseBody: ` + { + "enabled": true + }`, + expectedResponse: &GetIPAllowlistStatusResponse{ + Enabled: true, + }, + }, + "200 OK enabled false": { + responseStatus: 200, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/status", + responseBody: ` + { + "enabled": false + }`, + expectedResponse: &GetIPAllowlistStatusResponse{ + Enabled: false, + }, + }, + "500 internal server error": { + responseStatus: 500, + expectedPath: "/identity-management/v3/user-admin/ip-acl/allowlist/status", + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +} +`, + withError: func(t *testing.T, e error) { + err := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + StatusCode: 500, + Detail: "Error making request", + } + assert.Equal(t, true, err.Is(e)) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetIPAllowlistStatus(context.Background()) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} diff --git a/pkg/iam/mocks.go b/pkg/iam/mocks.go index f3b5a392..b93dbfe1 100644 --- a/pkg/iam/mocks.go +++ b/pkg/iam/mocks.go @@ -74,6 +74,16 @@ func (m *Mock) SupportedTimezones(ctx context.Context) ([]Timezone, error) { return args.Get(0).([]Timezone), args.Error(1) } +func (m *Mock) GetPasswordPolicy(ctx context.Context) (*GetPasswordPolicyResponse, error) { + args := m.Called(ctx) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*GetPasswordPolicyResponse), args.Error(1) +} + func (m *Mock) ListProducts(ctx context.Context) ([]string, error) { args := m.Called(ctx) @@ -94,6 +104,16 @@ func (m *Mock) ListTimeoutPolicies(ctx context.Context) ([]TimeoutPolicy, error) return args.Get(0).([]TimeoutPolicy), args.Error(1) } +func (m *Mock) ListAccountSwitchKeys(ctx context.Context, request ListAccountSwitchKeysRequest) (ListAccountSwitchKeysResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListAccountSwitchKeysResponse), args.Error(1) +} + func (m *Mock) ListStates(ctx context.Context, request ListStatesRequest) ([]string, error) { args := m.Called(ctx, request) @@ -301,6 +321,18 @@ func (m *Mock) UpdateTFA(ctx context.Context, request UpdateTFARequest) error { return args.Error(0) } +func (m *Mock) UpdateMFA(ctx context.Context, request UpdateMFARequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + +func (m *Mock) ResetMFA(ctx context.Context, request ResetMFARequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + func (m *Mock) ResetUserPassword(ctx context.Context, request ResetUserPasswordRequest) (*ResetUserPasswordResponse, error) { args := m.Called(ctx, request) @@ -317,14 +349,24 @@ func (m *Mock) SetUserPassword(ctx context.Context, request SetUserPasswordReque return args.Error(0) } -func (m *Mock) ListProperties(ctx context.Context, request ListPropertiesRequest) (*ListPropertiesResponse, error) { +func (m *Mock) ListProperties(ctx context.Context, request ListPropertiesRequest) (ListPropertiesResponse, error) { args := m.Called(ctx, request) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*ListPropertiesResponse), args.Error(1) + return args.Get(0).(ListPropertiesResponse), args.Error(1) +} + +func (m *Mock) ListUsersForProperty(ctx context.Context, request ListUsersForPropertyRequest) (ListUsersForPropertyResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListUsersForPropertyResponse), args.Error(1) } func (m *Mock) GetProperty(ctx context.Context, request GetPropertyRequest) (*GetPropertyResponse, error) { @@ -362,3 +404,251 @@ func (m *Mock) MapPropertyNameToID(ctx context.Context, request MapPropertyNameT return args.Get(0).(*int64), args.Error(1) } + +func (m *Mock) LockAPIClient(ctx context.Context, request LockAPIClientRequest) (*LockAPIClientResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*LockAPIClientResponse), args.Error(1) +} + +func (m *Mock) UnlockAPIClient(ctx context.Context, request UnlockAPIClientRequest) (*UnlockAPIClientResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*UnlockAPIClientResponse), args.Error(1) +} + +func (m *Mock) DisableIPAllowlist(ctx context.Context) error { + args := m.Called(ctx) + + return args.Error(0) +} + +func (m *Mock) EnableIPAllowlist(ctx context.Context) error { + args := m.Called(ctx) + + return args.Error(0) +} + +func (m *Mock) GetIPAllowlistStatus(ctx context.Context) (*GetIPAllowlistStatusResponse, error) { + args := m.Called(ctx) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*GetIPAllowlistStatusResponse), args.Error(1) +} + +func (m *Mock) ListAllowedCPCodes(ctx context.Context, params ListAllowedCPCodesRequest) (ListAllowedCPCodesResponse, error) { + args := m.Called(ctx, params) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListAllowedCPCodesResponse), args.Error(1) +} + +func (m *Mock) ListCIDRBlocks(ctx context.Context, request ListCIDRBlocksRequest) (ListCIDRBlocksResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListCIDRBlocksResponse), args.Error(1) +} + +func (m *Mock) CreateCIDRBlock(ctx context.Context, request CreateCIDRBlockRequest) (*CreateCIDRBlockResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*CreateCIDRBlockResponse), args.Error(1) +} + +func (m *Mock) GetCIDRBlock(ctx context.Context, request GetCIDRBlockRequest) (*GetCIDRBlockResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*GetCIDRBlockResponse), args.Error(1) +} + +func (m *Mock) UpdateCIDRBlock(ctx context.Context, request UpdateCIDRBlockRequest) (*UpdateCIDRBlockResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*UpdateCIDRBlockResponse), args.Error(1) +} + +func (m *Mock) DeleteCIDRBlock(ctx context.Context, request DeleteCIDRBlockRequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + +func (m *Mock) ValidateCIDRBlock(ctx context.Context, request ValidateCIDRBlockRequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + +func (m *Mock) BlockUsers(ctx context.Context, request BlockUsersRequest) (*BlockUsersResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*BlockUsersResponse), args.Error(1) +} + +func (m *Mock) ListAuthorizedUsers(ctx context.Context) (ListAuthorizedUsersResponse, error) { + args := m.Called(ctx) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListAuthorizedUsersResponse), args.Error(1) +} + +func (m *Mock) ListAllowedAPIs(ctx context.Context, request ListAllowedAPIsRequest) (ListAllowedAPIsResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListAllowedAPIsResponse), args.Error(1) +} + +func (m *Mock) ListAccessibleGroups(ctx context.Context, request ListAccessibleGroupsRequest) (ListAccessibleGroupsResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListAccessibleGroupsResponse), args.Error(1) +} + +func (m *Mock) CreateCredential(ctx context.Context, request CreateCredentialRequest) (*CreateCredentialResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*CreateCredentialResponse), args.Error(1) +} + +func (m *Mock) ListCredentials(ctx context.Context, request ListCredentialsRequest) (ListCredentialsResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListCredentialsResponse), args.Error(1) +} + +func (m *Mock) GetCredential(ctx context.Context, request GetCredentialRequest) (*GetCredentialResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*GetCredentialResponse), args.Error(1) +} + +func (m *Mock) UpdateCredential(ctx context.Context, request UpdateCredentialRequest) (*UpdateCredentialResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*UpdateCredentialResponse), args.Error(1) +} + +func (m *Mock) DeleteCredential(ctx context.Context, request DeleteCredentialRequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + +func (m *Mock) DeactivateCredential(ctx context.Context, request DeactivateCredentialRequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + +func (m *Mock) DeactivateCredentials(ctx context.Context, request DeactivateCredentialsRequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} + +func (m *Mock) ListAPIClients(ctx context.Context, request ListAPIClientsRequest) (ListAPIClientsResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(ListAPIClientsResponse), args.Error(1) +} + +func (m *Mock) GetAPIClient(ctx context.Context, request GetAPIClientRequest) (*GetAPIClientResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*GetAPIClientResponse), args.Error(1) +} + +func (m *Mock) CreateAPIClient(ctx context.Context, request CreateAPIClientRequest) (*CreateAPIClientResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*CreateAPIClientResponse), args.Error(1) +} + +func (m *Mock) UpdateAPIClient(ctx context.Context, request UpdateAPIClientRequest) (*UpdateAPIClientResponse, error) { + args := m.Called(ctx, request) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*UpdateAPIClientResponse), args.Error(1) +} + +func (m *Mock) DeleteAPIClient(ctx context.Context, request DeleteAPIClientRequest) error { + args := m.Called(ctx, request) + + return args.Error(0) +} diff --git a/pkg/iam/properties.go b/pkg/iam/properties.go index 415108c8..66c5aedb 100644 --- a/pkg/iam/properties.go +++ b/pkg/iam/properties.go @@ -9,53 +9,45 @@ import ( "strconv" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Properties is the IAM properties API interface - Properties interface { - // ListProperties lists the properties for the current account or other managed accounts using the accountSwitchKey parameter. - // - // See: https://techdocs.akamai.com/iam-api/reference/get-properties - ListProperties(context.Context, ListPropertiesRequest) (*ListPropertiesResponse, error) - - // GetProperty lists a property's details. - // - // See: https://techdocs.akamai.com/iam-api/reference/get-property - GetProperty(context.Context, GetPropertyRequest) (*GetPropertyResponse, error) - - // MoveProperty moves a property from one group to another group. - // - // See: https://techdocs.akamai.com/iam-api/reference/put-property - MoveProperty(context.Context, MovePropertyRequest) error - - // MapPropertyIDToName returns property name for given (IAM) property ID - // Mainly to be used to map (IAM) Property ID to (PAPI) Property ID - // To finish the mapping, please use papi.MapPropertyNameToID - MapPropertyIDToName(context.Context, MapPropertyIDToNameRequest) (*string, error) - } - - // ListPropertiesRequest contains the request parameters for the list properties operation. + // ListPropertiesRequest contains the request parameters for the ListProperties endpoint. ListPropertiesRequest struct { GroupID int64 Actions bool } - // GetPropertyRequest contains the request parameters for the get property operation. + // ListUsersForPropertyRequest contains the request parameters for the ListUsersForProperty endpoint. + ListUsersForPropertyRequest struct { + PropertyID int64 + UserType PropertyUserType + } + + // GetPropertyRequest contains the request parameters for the GetProperty endpoint. GetPropertyRequest struct { PropertyID int64 GroupID int64 } - // MapPropertyNameToIDRequest is the argument for MapPropertyNameToID + // MapPropertyNameToIDRequest is the argument for MapPropertyNameToID. MapPropertyNameToIDRequest string - // ListPropertiesResponse holds the response data from ListProperties. + // BlockUsersRequest contains the request parameters for the BlockUsers endpoint. + BlockUsersRequest struct { + PropertyID int64 + Body BlockUsersRequestBody + } + + // ListPropertiesResponse holds the response data from the ListProperties endpoint. ListPropertiesResponse []Property - // GetPropertyResponse holds the response data from GetProperty. + // ListUsersForPropertyResponse holds the response data from the ListUsersForProperty endpoint. + ListUsersForPropertyResponse []UsersForProperty + + // GetPropertyResponse holds the response data from the GetProperty endpoint. GetPropertyResponse struct { ARLConfigFile string `json:"arlConfigFile"` CreatedBy string `json:"createdBy"` @@ -68,18 +60,29 @@ type ( PropertyName string `json:"propertyName"` } - // MovePropertyRequest contains the request parameters for the MoveProperty operation. + // BlockUsersResponse holds the response data from the BlockUsers endpoint. + BlockUsersResponse []UsersForProperty + + // MovePropertyRequest contains the request parameters for the MoveProperty endpoint. MovePropertyRequest struct { PropertyID int64 - BodyParams MovePropertyReqBody + Body MovePropertyRequestBody } - // MovePropertyReqBody contains body parameters for the MoveProperty operation. - MovePropertyReqBody struct { + // MovePropertyRequestBody contains body parameters for the MoveProperty endpoint. + MovePropertyRequestBody struct { DestinationGroupID int64 `json:"destinationGroupId"` SourceGroupID int64 `json:"sourceGroupId"` } + // BlockUsersRequestBody hold the request body parameters for the BlockUsers endpoint. + BlockUsersRequestBody []BlockUserItem + + // BlockUserItem contains body parameters for the BlockUsers endpoint. + BlockUserItem struct { + UIIdentityID string `json:"uiIdentityId"` + } + // Property holds the property details. Property struct { PropertyID int64 `json:"propertyId"` @@ -95,14 +98,51 @@ type ( Move bool `json:"move"` } - // MapPropertyIDToNameRequest is the argument for MapPropertyIDToName + // MapPropertyIDToNameRequest is the argument for MapPropertyIDToName. MapPropertyIDToNameRequest struct { PropertyID int64 GroupID int64 } + + // UsersForProperty holds details about the users accessing the property. + UsersForProperty struct { + FirstName string `json:"firstName"` + IsBlocked bool `json:"isBlocked"` + LastName string `json:"lastName"` + UIIdentityID string `json:"uiIdentityId"` + UIUserName string `json:"uiUserName"` + } + + // PropertyUserType filters property users based on their access to the property. + PropertyUserType string ) -// Validate validates GetPropertyRequest +const ( + // PropertyUserTypeAll selects all property users. + PropertyUserTypeAll PropertyUserType = "all" + // PropertyUserTypeAssigned selects users that have access to the property. + PropertyUserTypeAssigned PropertyUserType = "assigned" + // PropertyUserTypeBlocked selects users whose access to the property is blocked. + PropertyUserTypeBlocked PropertyUserType = "blocked" +) + +// Validate validates PropertyUserType. +func (p PropertyUserType) Validate() error { + return validation.In(PropertyUserTypeAll, PropertyUserTypeAssigned, PropertyUserTypeBlocked). + Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s' or '%s'", + p, PropertyUserTypeAll, PropertyUserTypeAssigned, PropertyUserTypeBlocked)). + Validate(p) +} + +// Validate validates ListUsersForPropertyRequest. +func (r ListUsersForPropertyRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "PropertyID": validation.Validate(r.PropertyID, validation.Required), + "UserType": r.UserType.Validate(), + }) +} + +// Validate validates GetPropertyRequest. func (r GetPropertyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "PropertyID": validation.Validate(r.PropertyID, validation.Required), @@ -110,7 +150,7 @@ func (r GetPropertyRequest) Validate() error { }) } -// Validate validates MapPropertyIDToNameRequest +// Validate validates MapPropertyIDToNameRequest. func (r MapPropertyIDToNameRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "PropertyID": validation.Validate(r.PropertyID, validation.Required), @@ -118,38 +158,57 @@ func (r MapPropertyIDToNameRequest) Validate() error { }) } -// Validate validates MovePropertyRequest +// Validate validates MovePropertyRequest. func (r MovePropertyRequest) Validate() error { return edgegriderr.ParseValidationErrors(validation.Errors{ "PropertyID": validation.Validate(r.PropertyID, validation.Required), - "BodyParams": validation.Validate(r.BodyParams, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), }) } -// Validate validates MovePropertyReqBody -func (r MovePropertyReqBody) Validate() error { - return edgegriderr.ParseValidationErrors(validation.Errors{ +// Validate validates MovePropertyRequestBody. +func (r MovePropertyRequestBody) Validate() error { + return validation.Errors{ "DestinationGroupID": validation.Validate(r.DestinationGroupID, validation.Required), "SourceGroupID": validation.Validate(r.SourceGroupID, validation.Required), + }.Filter() +} + +// Validate validates BlockUsersRequest. +func (r BlockUsersRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "PropertyID": validation.Validate(r.PropertyID, validation.Required), + "Body": validation.Validate(r.Body, validation.Required), + }) +} + +// Validate validates BlockUserItem. +func (r BlockUserItem) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "UIIdentityID": validation.Validate(r.UIIdentityID, validation.Required), }) } var ( - // ErrListProperties is returned when ListProperties fails + // ErrListProperties is returned when ListProperties fails. ErrListProperties = errors.New("list properties") - // ErrGetProperty is returned when GetProperty fails + // ErrListUsersForProperty is returned when ListUsersForProperty fails. + ErrListUsersForProperty = errors.New("list users for property") + // ErrGetProperty is returned when GetProperty fails. ErrGetProperty = errors.New("get property") - // ErrMoveProperty is returned when MoveProperty fails + // ErrMoveProperty is returned when MoveProperty fails. ErrMoveProperty = errors.New("move property") - // ErrMapPropertyIDToName is returned when MapPropertyIDToName fails + // ErrMapPropertyIDToName is returned when MapPropertyIDToName fails. ErrMapPropertyIDToName = errors.New("map property by id") - // ErrMapPropertyNameToID is returned when MapPropertyNameToID fails + // ErrMapPropertyNameToID is returned when MapPropertyNameToID fails. ErrMapPropertyNameToID = errors.New("map property by name") - // ErrNoProperty is returned when MapPropertyNameToID did not find given property + // ErrNoProperty is returned when MapPropertyNameToID did not find given property. ErrNoProperty = errors.New("no such property") + // ErrBlockUsers is returned when BlockUsers fails. + ErrBlockUsers = errors.New("block users") ) -func (i *iam) ListProperties(ctx context.Context, params ListPropertiesRequest) (*ListPropertiesResponse, error) { +func (i *iam) ListProperties(ctx context.Context, params ListPropertiesRequest) (ListPropertiesResponse, error) { logger := i.Log(ctx) logger.Debug("ListProperties") @@ -180,7 +239,44 @@ func (i *iam) ListProperties(ctx context.Context, params ListPropertiesRequest) return nil, fmt.Errorf("%s: %w", ErrListProperties, i.Error(resp)) } - return &result, nil + return result, nil +} + +func (i *iam) ListUsersForProperty(ctx context.Context, params ListUsersForPropertyRequest) (ListUsersForPropertyResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListUsersForProperty") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w:\n%s", ErrListUsersForProperty, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/properties/%d/users", params.PropertyID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListUsersForProperty, err) + } + + if params.UserType != "" { + q := uri.Query() + q.Add("userType", string(params.UserType)) + uri.RawQuery = q.Encode() + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListUsersForProperty, err) + } + + var result ListUsersForPropertyResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListUsersForProperty, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListUsersForProperty, i.Error(resp)) + } + + return result, nil } func (i *iam) GetProperty(ctx context.Context, params GetPropertyRequest) (*GetPropertyResponse, error) { @@ -233,7 +329,7 @@ func (i *iam) MoveProperty(ctx context.Context, params MovePropertyRequest) erro return fmt.Errorf("%w: failed to create request: %s", ErrMoveProperty, err) } - resp, err := i.Exec(req, nil, params.BodyParams) + resp, err := i.Exec(req, nil, params.Body) if err != nil { return fmt.Errorf("%w: request failed: %s", ErrMoveProperty, err) } @@ -279,7 +375,7 @@ func (i *iam) MapPropertyNameToID(ctx context.Context, name MapPropertyNameToIDR return nil, fmt.Errorf("%w: request failed: %s", ErrMapPropertyNameToID, err) } - for _, property := range *properties { + for _, property := range properties { if property.PropertyName == string(name) { return &property.PropertyID, nil } @@ -287,3 +383,31 @@ func (i *iam) MapPropertyNameToID(ctx context.Context, name MapPropertyNameToIDR return nil, fmt.Errorf("%w: %s", ErrNoProperty, name) } + +func (i *iam) BlockUsers(ctx context.Context, params BlockUsersRequest) (*BlockUsersResponse, error) { + logger := i.Log(ctx) + logger.Debug("BlockUsers") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrBlockUsers, ErrStructValidation, err) + } + + uri := fmt.Sprintf("/identity-management/v3/user-admin/properties/%d/users/block", params.PropertyID) + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrBlockUsers, err) + } + + var result BlockUsersResponse + resp, err := i.Exec(req, &result, params.Body) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrBlockUsers, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrBlockUsers, i.Error(resp)) + } + + return &result, nil +} diff --git a/pkg/iam/properties_test.go b/pkg/iam/properties_test.go index a0a130e1..e4e7d9bb 100644 --- a/pkg/iam/properties_test.go +++ b/pkg/iam/properties_test.go @@ -8,19 +8,19 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/internal/test" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/ptr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestListProperties(t *testing.T) { +func TestIAM_ListProperties(t *testing.T) { tests := map[string]struct { params ListPropertiesRequest responseStatus int expectedPath string responseBody string - expectedResponse *ListPropertiesResponse + expectedResponse ListPropertiesResponse withError func(*testing.T, error) }{ "200 OK - no query params": { @@ -45,7 +45,7 @@ func TestListProperties(t *testing.T) { } ] `, - expectedResponse: &ListPropertiesResponse{ + expectedResponse: ListPropertiesResponse{ { PropertyID: 1, PropertyName: "property1", @@ -83,7 +83,7 @@ func TestListProperties(t *testing.T) { } ] `, - expectedResponse: &ListPropertiesResponse{ + expectedResponse: ListPropertiesResponse{ { PropertyID: 1, PropertyName: "property1", @@ -101,7 +101,7 @@ func TestListProperties(t *testing.T) { responseStatus: http.StatusOK, expectedPath: "/identity-management/v3/user-admin/properties?actions=false", responseBody: `[]`, - expectedResponse: &ListPropertiesResponse{}, + expectedResponse: ListPropertiesResponse{}, }, "500 internal server error": { params: ListPropertiesRequest{}, @@ -126,28 +126,145 @@ func TestListProperties(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - users, err := client.ListProperties(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + users, err := client.ListProperties(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) - assert.Equal(t, test.expectedResponse, users) + assert.Equal(t, tc.expectedResponse, users) }) } } -func TestGetProperty(t *testing.T) { +func TestIAM_ListUserForProperty(t *testing.T) { + tests := map[string]struct { + params ListUsersForPropertyRequest + responseStatus int + expectedPath string + responseBody string + expectedResponse ListUsersForPropertyResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: ListUsersForPropertyRequest{ + PropertyID: 1, + }, + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/user-admin/properties/1/users", + responseBody: ` +[ + { + "firstName": "John", + "isBlocked": true, + "lastName": "Doe", + "uiIdentityId": "A-test-1234", + "uiUserName": "jdoe" + }, + { + "firstName": "Jan", + "isBlocked": false, + "lastName": "Kowalski", + "uiIdentityId": "A-test-12345", + "uiUserName": "jkowalski" + } +] +`, + expectedResponse: ListUsersForPropertyResponse{ + { + FirstName: "John", + IsBlocked: true, + LastName: "Doe", + UIIdentityID: "A-test-1234", + UIUserName: "jdoe", + }, + { + FirstName: "Jan", + IsBlocked: false, + LastName: "Kowalski", + UIIdentityID: "A-test-12345", + UIUserName: "jkowalski", + }, + }, + }, + "validation errors - blank PropertyID": { + params: ListUsersForPropertyRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "list users for property: struct validation:\n"+ + "PropertyID: cannot be blank", err.Error()) + }, + }, + "validation errors - bad UserType": { + params: ListUsersForPropertyRequest{ + PropertyID: 1, + UserType: "foo", + }, + withError: func(t *testing.T, err error) { + assert.Equal(t, "list users for property: struct validation:\n"+ + "UserType: value 'foo' is invalid. Must be one of: 'all', 'assigned' or 'blocked'", + err.Error()) + }, + }, + "404 not found": { + params: ListUsersForPropertyRequest{ + PropertyID: 1, + UserType: PropertyUserTypeAssigned, + }, + responseStatus: http.StatusNotFound, + expectedPath: "/identity-management/v3/user-admin/properties/1/users?userType=assigned", + responseBody: ` +{ + "instance": "", + "httpStatus": 404, + "detail": "", + "title": "Property not found", + "type": "/useradmin-api/error-types/1806" +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "/useradmin-api/error-types/1806", + Title: "Property not found", + StatusCode: http.StatusNotFound, + HTTPStatus: http.StatusNotFound, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + users, err := client.ListUsersForProperty(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expectedResponse, users) + }) + } +} + +func TestIAM_GetProperty(t *testing.T) { tests := map[string]struct { params GetPropertyRequest responseStatus int @@ -222,28 +339,28 @@ func TestGetProperty(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - users, err := client.GetProperty(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + users, err := client.GetProperty(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) - assert.Equal(t, test.expectedResponse, users) + assert.Equal(t, tc.expectedResponse, users) }) } } -func TestMoveProperty(t *testing.T) { +func TestIAM_MoveProperty(t *testing.T) { tests := map[string]struct { params MovePropertyRequest expectedPath string @@ -255,7 +372,7 @@ func TestMoveProperty(t *testing.T) { "204 OK": { params: MovePropertyRequest{ PropertyID: 1, - BodyParams: MovePropertyReqBody{ + Body: MovePropertyRequestBody{ DestinationGroupID: 22, SourceGroupID: 11, }, @@ -271,13 +388,13 @@ func TestMoveProperty(t *testing.T) { "validation errors": { params: MovePropertyRequest{}, withError: func(t *testing.T, err error) { - assert.Equal(t, "move property: struct validation: BodyParams: DestinationGroupID: cannot be blank\nSourceGroupID: cannot be blank\nPropertyID: cannot be blank", err.Error()) + assert.Equal(t, "move property: struct validation: DestinationGroupID: cannot be blank\nSourceGroupID: cannot be blank\nPropertyID: cannot be blank", err.Error()) }, }, "400 not allowed": { params: MovePropertyRequest{ PropertyID: 1, - BodyParams: MovePropertyReqBody{ + Body: MovePropertyRequestBody{ DestinationGroupID: 22, SourceGroupID: 11, }, @@ -306,26 +423,26 @@ func TestMoveProperty(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - if test.expectedRequestBody != "" { + if tc.expectedRequestBody != "" { body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.JSONEq(t, test.expectedRequestBody, string(body)) + assert.JSONEq(t, tc.expectedRequestBody, string(body)) } - w.WriteHeader(test.responseStatus) - if test.responseBody != "" { - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + if tc.responseBody != "" { + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) } })) client := mockAPIClient(t, mockServer) - err := client.MoveProperty(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + err := client.MoveProperty(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) @@ -333,7 +450,7 @@ func TestMoveProperty(t *testing.T) { } } -func TestMapPropertyIDToName(t *testing.T) { +func TestIAM_MapPropertyIDToName(t *testing.T) { tests := map[string]struct { params MapPropertyIDToNameRequest responseStatus int @@ -396,22 +513,197 @@ func TestMapPropertyIDToName(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - users, err := client.MapPropertyIDToName(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + users, err := client.MapPropertyIDToName(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expectedResponse, users) + }) + } +} + +func TestIAM_BlockUsers(t *testing.T) { + tests := map[string]struct { + params BlockUsersRequest + expectedPath string + expectedRequestBody string + responseStatus int + responseBody string + expectedResponse *BlockUsersResponse + withError func(*testing.T, error) + }{ + "200 OK": { + params: BlockUsersRequest{ + PropertyID: 1, + Body: BlockUsersRequestBody{ + BlockUserItem{ + UIIdentityID: "A-test-1234", + }, + BlockUserItem{ + UIIdentityID: "A-test-12345", + }, + }, + }, + expectedRequestBody: ` +[ + { + "uiIdentityId": "A-test-1234" + }, + { + "uiIdentityId": "A-test-12345" + } +]`, + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/user-admin/properties/1/users/block", + responseBody: ` +[ + { + "firstName": "John", + "isBlocked": true, + "lastName": "Doe", + "uiIdentityId": "A-test-1234", + "uiUserName": "jdoe" + }, + { + "firstName": "Jan", + "isBlocked": true, + "lastName": "Kowalski", + "uiIdentityId": "A-test-12345", + "uiUserName": "jkowalski" + } +] +`, + expectedResponse: &BlockUsersResponse{ + { + FirstName: "John", + IsBlocked: true, + LastName: "Doe", + UIIdentityID: "A-test-1234", + UIUserName: "jdoe", + }, + { + FirstName: "Jan", + IsBlocked: true, + LastName: "Kowalski", + UIIdentityID: "A-test-12345", + UIUserName: "jkowalski", + }, + }, + }, + "validation errors - no params": { + params: BlockUsersRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "block users: struct validation: Body: cannot be blank\nPropertyID: cannot be blank", err.Error()) + }, + }, + "validation errors - empty body": { + params: BlockUsersRequest{ + PropertyID: 1, + Body: BlockUsersRequestBody{}, + }, + withError: func(t *testing.T, err error) { + assert.Equal(t, "block users: struct validation: Body: cannot be blank", err.Error()) + }, + }, + "404 invalid identity": { + params: BlockUsersRequest{ + PropertyID: 1, + Body: BlockUsersRequestBody{ + BlockUserItem{ + UIIdentityID: "test", + }, + }, + }, + responseStatus: http.StatusNotFound, + expectedPath: "/identity-management/v3/user-admin/properties/1/users/block", + responseBody: ` +{ + "instance": "", + "httpStatus": 404, + "detail": "", + "title": "Identities [test] are not valid.", + "type": "/useradmin-api/error-types/1100" +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "/useradmin-api/error-types/1100", + Title: "Identities [test] are not valid.", + Detail: "", + HTTPStatus: http.StatusNotFound, + StatusCode: http.StatusNotFound, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "404 not found": { + params: BlockUsersRequest{ + PropertyID: 2, + Body: BlockUsersRequestBody{ + BlockUserItem{ + UIIdentityID: "A-test-1234", + }, + }, + }, + responseStatus: http.StatusNotFound, + expectedPath: "/identity-management/v3/user-admin/properties/2/users/block", + responseBody: ` +{ + "instance": "", + "httpStatus": 404, + "detail": "", + "title": "Property not found", + "type": "/useradmin-api/error-types/1806" +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "/useradmin-api/error-types/1806", + Title: "Property not found", + Detail: "", + HTTPStatus: http.StatusNotFound, + StatusCode: http.StatusNotFound, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + if tc.expectedRequestBody != "" { + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, tc.expectedRequestBody, string(body)) + } + w.WriteHeader(tc.responseStatus) + if tc.responseBody != "" { + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + } + })) + client := mockAPIClient(t, mockServer) + users, err := client.BlockUsers(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) - assert.Equal(t, test.expectedResponse, users) + assert.Equal(t, tc.expectedResponse, users) }) } } diff --git a/pkg/iam/roles.go b/pkg/iam/roles.go index e1e52e36..6c032bf8 100644 --- a/pkg/iam/roles.go +++ b/pkg/iam/roles.go @@ -7,60 +7,29 @@ import ( "net/http" "net/url" "strconv" + "time" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Roles is the IAM role API interface - Roles interface { - // CreateRole creates a custom role - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-role - CreateRole(context.Context, CreateRoleRequest) (*Role, error) - - // GetRole gets details for a specific role - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-role - GetRole(context.Context, GetRoleRequest) (*Role, error) - - // UpdateRole adds or removes permissions from a role and updates other parameters - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-role - UpdateRole(context.Context, UpdateRoleRequest) (*Role, error) - - // DeleteRole deletes a role. This operation is only allowed if the role isn't assigned to any users. - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/delete-role - DeleteRole(context.Context, DeleteRoleRequest) error - - // ListRoles lists roles for the current account and contract type - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-roles - ListRoles(context.Context, ListRolesRequest) ([]Role, error) - - // ListGrantableRoles lists which grantable roles can be included in a new custom role or added to an existing custom role - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-grantable-roles - ListGrantableRoles(context.Context) ([]RoleGrantedRole, error) - } - - // RoleRequest describes request parameters of the create and update role endpoint + // RoleRequest describes request parameters for the CreateRole and UpdateRole endpoints. RoleRequest struct { Name string `json:"roleName,omitempty"` Description string `json:"roleDescription,omitempty"` GrantedRoles []GrantedRoleID `json:"grantedRoles,omitempty"` } - // CreateRoleRequest describes the request parameters of the create role endpoint + // CreateRoleRequest describes the request parameters for the CreateRole endpoint. CreateRoleRequest RoleRequest - // GrantedRoleID describes a unique identifier for a granted role + // GrantedRoleID describes a unique identifier for a granted role. GrantedRoleID struct { ID int64 `json:"grantedRoleId"` } - // GetRoleRequest describes the request parameters of the get role endpoint + // GetRoleRequest describes the request parameters for the GetRole endpoint. GetRoleRequest struct { ID int64 Actions bool @@ -68,19 +37,19 @@ type ( Users bool } - // UpdateRoleRequest describes the request parameters of the update role endpoint. + // UpdateRoleRequest describes the request parameters for the UpdateRole endpoint. // It works as patch request. You need to provide only fields which you want to update. UpdateRoleRequest struct { ID int64 RoleRequest } - // DeleteRoleRequest describes the request parameters of the delete role endpoint + // DeleteRoleRequest describes the request parameters for the DeleteRole endpoint. DeleteRoleRequest struct { ID int64 } - // ListRolesRequest describes the request parameters of the list roles endpoint + // ListRolesRequest describes the request parameters for the ListRoles endpoint. ListRolesRequest struct { GroupID *int64 Actions bool @@ -88,37 +57,37 @@ type ( Users bool } - // RoleAction encapsulates permissions available to the user for this role + // RoleAction encapsulates permissions available to the user for this role. RoleAction struct { Delete bool `json:"delete"` Edit bool `json:"edit"` } - // RoleGrantedRole is a list of granted roles, giving the user access to objects in a group + // RoleGrantedRole is a list of granted roles, giving the user access to objects in a group. RoleGrantedRole struct { Description string `json:"grantedRoleDescription,omitempty"` RoleID int64 `json:"grantedRoleId"` RoleName string `json:"grantedRoleName"` } - // RoleUser user who shares the same role + // RoleUser user who shares the same role. RoleUser struct { - AccountID string `json:"accountId"` - Email string `json:"email"` - FirstName string `json:"firstName"` - LastLoginDate string `json:"lastLoginDate"` - LastName string `json:"lastName"` - UIIdentityID string `json:"uiIdentityId"` + AccountID string `json:"accountId"` + Email string `json:"email"` + FirstName string `json:"firstName"` + LastLoginDate time.Time `json:"lastLoginDate"` + LastName string `json:"lastName"` + UIIdentityID string `json:"uiIdentityId"` } - // Role encapsulates the response of the list roles endpoint + // Role encapsulates the response from the ListRoles endpoint. Role struct { Actions *RoleAction `json:"actions,omitempty"` CreatedBy string `json:"createdBy"` - CreatedDate string `json:"createdDate"` + CreatedDate time.Time `json:"createdDate"` GrantedRoles []RoleGrantedRole `json:"grantedRoles,omitempty"` ModifiedBy string `json:"modifiedBy"` - ModifiedDate string `json:"modifiedDate"` + ModifiedDate time.Time `json:"modifiedDate"` RoleDescription string `json:"roleDescription"` RoleID int64 `json:"roleId"` RoleName string `json:"roleName"` @@ -126,60 +95,60 @@ type ( RoleType RoleType `json:"type"` } - // RoleType is an enum of role types + // RoleType is an enum of role types. RoleType string ) -// Validate validates CreateRoleRequest +// Validate validates CreateRoleRequest. func (r CreateRoleRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "Name": validation.Validate(r.Name, validation.Required), "Description": validation.Validate(r.Description, validation.Required), "GrantedRoles": validation.Validate(r.GrantedRoles, validation.Required), - }.Filter() + }) } -// Validate validates GetRoleRequest +// Validate validates GetRoleRequest. func (r GetRoleRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "ID": validation.Validate(r.ID, validation.Required), - }.Filter() + }) } -// Validate validates UpdateRoleRequest +// Validate validates UpdateRoleRequest. func (r UpdateRoleRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "ID": validation.Validate(r.ID, validation.Required), - }.Filter() + }) } -// Validate validates DeleteRoleRequest +// Validate validates DeleteRoleRequest. func (r DeleteRoleRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "ID": validation.Validate(r.ID, validation.Required), - }.Filter() + }) } var ( - // RoleTypeStandard is a standard type provided by Akamai + // RoleTypeStandard is a standard type provided by Akamai. RoleTypeStandard RoleType = "standard" - // RoleTypeCustom is a custom role provided by the account + // RoleTypeCustom is a custom role provided by the account. RoleTypeCustom RoleType = "custom" ) var ( - // ErrCreateRole is returned when CreateRole fails + // ErrCreateRole is returned when CreateRole fails. ErrCreateRole = errors.New("create a role") - // ErrGetRole is returned when GetRole fails + // ErrGetRole is returned when GetRole fails. ErrGetRole = errors.New("get a role") - // ErrUpdateRole is returned when UpdateRole fails + // ErrUpdateRole is returned when UpdateRole fails. ErrUpdateRole = errors.New("update a role") - // ErrDeleteRole is returned when DeleteRole fails + // ErrDeleteRole is returned when DeleteRole fails. ErrDeleteRole = errors.New("delete a role") - // ErrListRoles is returned when ListRoles fails + // ErrListRoles is returned when ListRoles fails. ErrListRoles = errors.New("list roles") - // ErrListGrantableRoles is returned when ListGrantableRoles fails + // ErrListGrantableRoles is returned when ListGrantableRoles fails. ErrListGrantableRoles = errors.New("list grantable roles") ) @@ -191,7 +160,7 @@ func (i *iam) CreateRole(ctx context.Context, params CreateRoleRequest) (*Role, return nil, fmt.Errorf("%s: %w:\n%s", ErrCreateRole, ErrStructValidation, err) } - uri, err := url.Parse("/identity-management/v2/user-admin/roles") + uri, err := url.Parse("/identity-management/v3/user-admin/roles") if err != nil { return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCreateRole, err) } @@ -222,7 +191,7 @@ func (i *iam) GetRole(ctx context.Context, params GetRoleRequest) (*Role, error) return nil, fmt.Errorf("%s: %w:\n%s", ErrGetRole, ErrStructValidation, err) } - uri, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/roles/%d", params.ID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/roles/%d", params.ID)) if err != nil { return nil, fmt.Errorf("%w: failed to parse url: %s", ErrGetRole, err) } @@ -260,7 +229,7 @@ func (i *iam) UpdateRole(ctx context.Context, params UpdateRoleRequest) (*Role, return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateRole, ErrStructValidation, err) } - uri, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/roles/%d", params.ID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/roles/%d", params.ID)) if err != nil { return nil, fmt.Errorf("%w: failed to parse url: %s", ErrUpdateRole, err) } @@ -291,7 +260,7 @@ func (i *iam) DeleteRole(ctx context.Context, params DeleteRoleRequest) error { return fmt.Errorf("%s: %w:\n%s", ErrDeleteRole, ErrStructValidation, err) } - uri, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/roles/%d", params.ID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/roles/%d", params.ID)) if err != nil { return fmt.Errorf("%w: failed to parse url: %s", ErrDeleteRole, err) } @@ -317,11 +286,11 @@ func (i *iam) ListRoles(ctx context.Context, params ListRolesRequest) ([]Role, e logger := i.Log(ctx) logger.Debug("ListRoles") - u, err := url.Parse("/identity-management/v2/user-admin/roles") + uri, err := url.Parse("/identity-management/v3/user-admin/roles") if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListRoles, err) } - q := u.Query() + q := uri.Query() q.Add("actions", strconv.FormatBool(params.Actions)) q.Add("ignoreContext", strconv.FormatBool(params.IgnoreContext)) q.Add("users", strconv.FormatBool(params.Users)) @@ -330,9 +299,9 @@ func (i *iam) ListRoles(ctx context.Context, params ListRolesRequest) ([]Role, e q.Add("groupId", strconv.FormatInt(*params.GroupID, 10)) } - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListRoles, err) } @@ -354,7 +323,7 @@ func (i *iam) ListGrantableRoles(ctx context.Context) ([]RoleGrantedRole, error) logger := i.Log(ctx) logger.Debug("ListGrantableRoles") - uri, err := url.Parse("/identity-management/v2/user-admin/roles/grantable-roles") + uri, err := url.Parse("/identity-management/v3/user-admin/roles/grantable-roles") if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListGrantableRoles, err) } diff --git a/pkg/iam/roles_test.go b/pkg/iam/roles_test.go index 1a451d3b..ab77deca 100644 --- a/pkg/iam/roles_test.go +++ b/pkg/iam/roles_test.go @@ -3,12 +3,13 @@ package iam import ( "context" "errors" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strconv" "testing" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -52,16 +53,16 @@ func TestIAM_CreateRole(t *testing.T) { } ] }`, - expectedPath: "/identity-management/v2/user-admin/roles", + expectedPath: "/identity-management/v3/user-admin/roles", expectedRequestBody: `{"roleName":"Terraform admin","roleDescription":"Admin granted role for tests","grantedRoles":[{"grantedRoleId":12345}]}`, expectedResponse: &Role{ RoleID: 123456, RoleName: "Terraform admin", RoleDescription: "Admin granted role for tests", RoleType: RoleTypeCustom, - CreatedDate: "2022-04-11T10:52:03.811Z", + CreatedDate: test.NewTimeFromString(t, "2022-04-11T10:52:03.811Z"), CreatedBy: "jBond", - ModifiedDate: "2022-04-11T10:52:03.811Z", + ModifiedDate: test.NewTimeFromString(t, "2022-04-11T10:52:03.811Z"), ModifiedBy: "jBond", Actions: &RoleAction{ Edit: true, @@ -90,7 +91,7 @@ func TestIAM_CreateRole(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/roles", + expectedPath: "/identity-management/v3/user-admin/roles", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -100,30 +101,30 @@ func TestIAM_CreateRole(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) - if len(test.expectedRequestBody) > 0 { - body, err := ioutil.ReadAll(r.Body) + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.Equal(t, test.expectedRequestBody, string(body)) + assert.Equal(t, tc.expectedRequestBody, string(body)) } })) client := mockAPIClient(t, mockServer) - result, err := client.CreateRole(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) - assert.Contains(t, err.Error(), strconv.FormatInt(int64(test.responseStatus), 10)) + result, err := client.CreateRole(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) + assert.Contains(t, err.Error(), strconv.FormatInt(int64(tc.responseStatus), 10)) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -190,15 +191,15 @@ func TestIAM_GetRole(t *testing.T) { } ] }`, - expectedPath: "/identity-management/v2/user-admin/roles/123456?actions=true&grantedRoles=true&users=true", + expectedPath: "/identity-management/v3/user-admin/roles/123456?actions=true&grantedRoles=true&users=true", expectedResponse: &Role{ RoleID: 123456, RoleName: "Terraform admin updated", RoleDescription: "Admin granted role for tests", RoleType: RoleTypeCustom, - CreatedDate: "2022-04-11T10:52:03.000Z", + CreatedDate: test.NewTimeFromString(t, "2022-04-11T10:52:03.000Z"), CreatedBy: "jBond", - ModifiedDate: "2022-04-11T10:59:30.000Z", + ModifiedDate: test.NewTimeFromString(t, "2022-04-11T10:59:30.000Z"), ModifiedBy: "jBond", Actions: &RoleAction{ Edit: true, @@ -223,7 +224,7 @@ func TestIAM_GetRole(t *testing.T) { LastName: "Smith", AccountID: "ACCOUNT1", Email: "example@akamai.com", - LastLoginDate: "2016-02-17T18:46:42.000Z", + LastLoginDate: test.NewTimeFromString(t, "2016-02-17T18:46:42.000Z"), }, { UIIdentityID: "USER2", @@ -231,7 +232,7 @@ func TestIAM_GetRole(t *testing.T) { LastName: "Smith", AccountID: "ACCOUNT2", Email: "example1@akamai.com", - LastLoginDate: "2016-02-17T18:46:42.000Z", + LastLoginDate: test.NewTimeFromString(t, "2016-02-17T18:46:42.000Z"), }, }, }, @@ -250,22 +251,22 @@ func TestIAM_GetRole(t *testing.T) { "modifiedDate": "2022-04-11T10:59:30.000Z", "modifiedBy": "jBond" }`, - expectedPath: "/identity-management/v2/user-admin/roles/123456?actions=false&grantedRoles=false&users=false", + expectedPath: "/identity-management/v3/user-admin/roles/123456?actions=false&grantedRoles=false&users=false", expectedResponse: &Role{ RoleID: 123456, RoleName: "Terraform admin updated", RoleDescription: "Admin granted role for tests", RoleType: RoleTypeCustom, - CreatedDate: "2022-04-11T10:52:03.000Z", + CreatedDate: test.NewTimeFromString(t, "2022-04-11T10:52:03.000Z"), CreatedBy: "jBond", - ModifiedDate: "2022-04-11T10:59:30.000Z", + ModifiedDate: test.NewTimeFromString(t, "2022-04-11T10:59:30.000Z"), ModifiedBy: "jBond", }, }, "404 Not found": { params: GetRoleRequest{ID: 123456}, responseStatus: http.StatusNotFound, - expectedPath: "/identity-management/v2/user-admin/roles/123456?actions=false&grantedRoles=false&users=false", + expectedPath: "/identity-management/v3/user-admin/roles/123456?actions=false&grantedRoles=false&users=false", responseBody: ` { "instance": "", @@ -293,7 +294,7 @@ func TestIAM_GetRole(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/roles/123456?actions=false&grantedRoles=false&users=false", + expectedPath: "/identity-management/v3/user-admin/roles/123456?actions=false&grantedRoles=false&users=false", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -303,24 +304,24 @@ func TestIAM_GetRole(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetRole(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) - assert.Contains(t, err.Error(), strconv.FormatInt(int64(test.responseStatus), 10)) + result, err := client.GetRole(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) + assert.Contains(t, err.Error(), strconv.FormatInt(int64(tc.responseStatus), 10)) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -378,16 +379,16 @@ func TestIAM_UpdateRole(t *testing.T) { } ] }`, - expectedPath: "/identity-management/v2/user-admin/roles/123456", + expectedPath: "/identity-management/v3/user-admin/roles/123456", expectedRequestBody: `{"roleName":"Terraform admin updated","grantedRoles":[{"grantedRoleId":54321},{"grantedRoleId":12345}]}`, expectedResponse: &Role{ RoleID: 123456, RoleName: "Terraform admin updated", RoleDescription: "Admin granted role for tests", RoleType: RoleTypeCustom, - CreatedDate: "2022-04-11T10:52:03.000Z", + CreatedDate: test.NewTimeFromString(t, "2022-04-11T10:52:03.000Z"), CreatedBy: "jBond", - ModifiedDate: "2022-04-11T10:59:30.000Z", + ModifiedDate: test.NewTimeFromString(t, "2022-04-11T10:59:30.000Z"), ModifiedBy: "jBond", Actions: &RoleAction{ Edit: true, @@ -422,7 +423,7 @@ func TestIAM_UpdateRole(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/roles/123456", + expectedPath: "/identity-management/v3/user-admin/roles/123456", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -432,30 +433,30 @@ func TestIAM_UpdateRole(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) - if len(test.expectedRequestBody) > 0 { - body, err := ioutil.ReadAll(r.Body) + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.Equal(t, test.expectedRequestBody, string(body)) + assert.Equal(t, tc.expectedRequestBody, string(body)) } })) client := mockAPIClient(t, mockServer) - result, err := client.UpdateRole(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) - assert.Contains(t, err.Error(), strconv.FormatInt(int64(test.responseStatus), 10)) + result, err := client.UpdateRole(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) + assert.Contains(t, err.Error(), strconv.FormatInt(int64(tc.responseStatus), 10)) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -471,12 +472,12 @@ func TestIAM_DeleteRole(t *testing.T) { "204 Deleted": { params: DeleteRoleRequest{ID: 123456}, responseStatus: http.StatusNoContent, - expectedPath: "/identity-management/v2/user-admin/roles/123456", + expectedPath: "/identity-management/v3/user-admin/roles/123456", }, "404 Not found": { params: DeleteRoleRequest{ID: 123456}, responseStatus: http.StatusNotFound, - expectedPath: "/identity-management/v2/user-admin/roles/123456", + expectedPath: "/identity-management/v3/user-admin/roles/123456", responseBody: ` { "instance": "", @@ -504,7 +505,7 @@ func TestIAM_DeleteRole(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/roles/123456", + expectedPath: "/identity-management/v3/user-admin/roles/123456", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -514,20 +515,20 @@ func TestIAM_DeleteRole(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodDelete, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.DeleteRole(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) - assert.Contains(t, err.Error(), strconv.FormatInt(int64(test.responseStatus), 10)) + err := client.DeleteRole(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) + assert.Contains(t, err.Error(), strconv.FormatInt(int64(tc.responseStatus), 10)) return } require.NoError(t, err) @@ -566,16 +567,16 @@ func TestIAM_ListRoles(t *testing.T) { } } ]`, - expectedPath: "/identity-management/v2/user-admin/roles?actions=true&ignoreContext=false&users=false", + expectedPath: "/identity-management/v3/user-admin/roles?actions=true&ignoreContext=false&users=false", expectedResponse: []Role{ { RoleID: 123456, RoleName: "View Only", RoleDescription: "This role will allow you to view", RoleType: RoleTypeCustom, - CreatedDate: "2017-07-27T18:11:25.000Z", + CreatedDate: test.NewTimeFromString(t, "2017-07-27T18:11:25.000Z"), CreatedBy: "john.doe@mycompany.com", - ModifiedDate: "2017-07-27T18:11:25.000Z", + ModifiedDate: test.NewTimeFromString(t, "2017-07-27T18:11:25.000Z"), ModifiedBy: "john.doe@mycompany.com", Actions: &RoleAction{ Edit: true, @@ -596,7 +597,7 @@ func TestIAM_ListRoles(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/roles?actions=true&ignoreContext=false&users=false", + expectedPath: "/identity-management/v3/user-admin/roles?actions=true&ignoreContext=false&users=false", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -606,24 +607,24 @@ func TestIAM_ListRoles(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.ListRoles(context.Background(), test.params) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) - assert.Contains(t, err.Error(), strconv.FormatInt(int64(test.responseStatus), 10)) + result, err := client.ListRoles(context.Background(), tc.params) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) + assert.Contains(t, err.Error(), strconv.FormatInt(int64(tc.responseStatus), 10)) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -651,7 +652,7 @@ func TestIAM_ListGrantableRoles(t *testing.T) { "grantedRoleDescription": "second role description" } ]`, - expectedPath: "/identity-management/v2/user-admin/roles/grantable-roles", + expectedPath: "/identity-management/v3/user-admin/roles/grantable-roles", expectedResponse: []RoleGrantedRole{ { RoleID: 123456, @@ -674,7 +675,7 @@ func TestIAM_ListGrantableRoles(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/roles/grantable-roles", + expectedPath: "/identity-management/v3/user-admin/roles/grantable-roles", withError: &Error{ Type: "internal_error", Title: "Internal Server Error", @@ -684,24 +685,24 @@ func TestIAM_ListGrantableRoles(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.ListGrantableRoles(context.Background()) - if test.withError != nil { - assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) - assert.Contains(t, err.Error(), strconv.FormatInt(int64(test.responseStatus), 10)) + if tc.withError != nil { + assert.True(t, errors.Is(err, tc.withError), "want: %s; got: %s", tc.withError, err) + assert.Contains(t, err.Error(), strconv.FormatInt(int64(tc.responseStatus), 10)) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } diff --git a/pkg/iam/support.go b/pkg/iam/support.go index 4e5598f9..382dae87 100644 --- a/pkg/iam/support.go +++ b/pkg/iam/support.go @@ -5,61 +5,53 @@ import ( "errors" "fmt" "net/http" + "net/url" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Support is a list of IAM supported objects API interfaces - Support interface { - // ListProducts lists products a user can subscribe to and receive notifications for on the account - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-notification-products - ListProducts(context.Context) ([]string, error) - - // ListStates lists U.S. states or Canadian provinces - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-states - ListStates(context.Context, ListStatesRequest) ([]string, error) - - // ListTimeoutPolicies lists all the possible session timeout policies - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-timeout-policies - ListTimeoutPolicies(context.Context) ([]TimeoutPolicy, error) - - // SupportedContactTypes lists supported contact types - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-contact-types - SupportedContactTypes(context.Context) ([]string, error) - - // SupportedCountries lists supported countries - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-countries - SupportedCountries(context.Context) ([]string, error) - - // SupportedLanguages lists supported languages - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-languages - SupportedLanguages(context.Context) ([]string, error) - - // SupportedTimezones lists supported timezones - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-common-timezones - SupportedTimezones(context.Context) ([]Timezone, error) - } - - // TimeoutPolicy encapsulates the response of the list timeout policies endpoint + // GetPasswordPolicyResponse holds the response data from the GetPasswordPolicy endpoint. + GetPasswordPolicyResponse struct { + CaseDiff int64 `json:"caseDif"` + MaxRepeating int64 `json:"maxRepeating"` + MinDigits int64 `json:"minDigits"` + MinLength int64 `json:"minLength"` + MinLetters int64 `json:"minLetters"` + MinNonAlpha int64 `json:"minNonAlpha"` + MinReuse int64 `json:"minReuse"` + PwClass string `json:"pwclass"` + RotateFrequency int64 `json:"rotateFrequency"` + } + + // TimeoutPolicy encapsulates the response from the ListTimeoutPolicies endpoint. TimeoutPolicy struct { Name string `json:"name"` Value int64 `json:"value"` } - // ListStatesRequest contains the country request parameter for the list states endpoint + // ListStatesRequest contains the country request parameter for the ListStates endpoint. ListStatesRequest struct { Country string } - // Timezone contains the response of the list supported timezones endpoint + // ListAccountSwitchKeysRequest contains the request parameters for the ListAccountSwitchKeys endpoint. + ListAccountSwitchKeysRequest struct { + ClientID string + Search string + } + + // ListAccountSwitchKeysResponse holds the response data from the ListAccountSwitchKeys endpoint. + ListAccountSwitchKeysResponse []AccountSwitchKey + + // AccountSwitchKey contains information about account switch key. + AccountSwitchKey struct { + AccountName string `json:"accountName"` + AccountSwitchKey string `json:"accountSwitchKey"` + } + + // Timezone contains the response from the ListSupportedTimezones endpoint. Timezone struct { Description string `json:"description"` Offset string `json:"offset"` @@ -69,48 +61,78 @@ type ( ) var ( - // ErrListProducts is returned when ListProducts fails + // ErrGetPasswordPolicy is returned when GetPasswordPolicy fails. + ErrGetPasswordPolicy = errors.New("get password policy") + + // ErrListProducts is returned when ListProducts fails. ErrListProducts = errors.New("list products") - // ErrListStates is returned when ListStates fails + // ErrListStates is returned when ListStates fails. ErrListStates = errors.New("list states") - // ErrListTimeoutPolicies is returned when ListTimeoutPolicies fails + // ErrListTimeoutPolicies is returned when ListTimeoutPolicies fails. ErrListTimeoutPolicies = errors.New("list timeout policies") - // ErrSupportedContactTypes is returned when SupportedContactTypes fails + // ErrListAccountSwitchKeys is returned when ListAccountSwitchKeys fails. + ErrListAccountSwitchKeys = errors.New("list account switch keys") + + // ErrSupportedContactTypes is returned when SupportedContactTypes fails. ErrSupportedContactTypes = errors.New("supported contact types") - // ErrSupportedCountries is returned when SupportedCountries fails + // ErrSupportedCountries is returned when SupportedCountries fails. ErrSupportedCountries = errors.New("supported countries") - // ErrSupportedLanguages is returned when SupportedLanguages fails + // ErrSupportedLanguages is returned when SupportedLanguages fails. ErrSupportedLanguages = errors.New("supported languages") - // ErrSupportedTimezones is returned when SupportedTimezones fails + // ErrSupportedTimezones is returned when SupportedTimezones fails. ErrSupportedTimezones = errors.New("supported timezones") ) -// Validate validates ListStatesRequest +// Validate validates ListStatesRequest. func (r ListStatesRequest) Validate() error { - return validation.Errors{ - "country": validation.Validate(r.Country, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Country": validation.Validate(r.Country, validation.Required), + }) +} + +func (i *iam) GetPasswordPolicy(ctx context.Context) (*GetPasswordPolicyResponse, error) { + logger := i.Log(ctx) + logger.Debug("GetPasswordPolicy") + + uri := "/identity-management/v3/user-admin/common/password-policy" + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetPasswordPolicy, err) + } + + var result GetPasswordPolicyResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrGetPasswordPolicy, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrGetPasswordPolicy, i.Error(resp)) + } + + return &result, nil } func (i *iam) ListProducts(ctx context.Context) ([]string, error) { logger := i.Log(ctx) logger.Debug("ListProducts") - getURL := "/identity-management/v2/user-admin/common/notification-products" + uri := "/identity-management/v3/user-admin/common/notification-products" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListProducts, err) } - var rval []string - resp, err := i.Exec(req, &rval) + var result []string + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrListProducts, err) } @@ -119,7 +141,7 @@ func (i *iam) ListProducts(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("%s: %w", ErrListProducts, i.Error(resp)) } - return rval, nil + return result, nil } func (i *iam) ListStates(ctx context.Context, params ListStatesRequest) ([]string, error) { @@ -130,15 +152,15 @@ func (i *iam) ListStates(ctx context.Context, params ListStatesRequest) ([]strin return nil, fmt.Errorf("%s: %w:\n%s", ErrListStates, ErrStructValidation, err) } - getURL := fmt.Sprintf("/identity-management/v2/user-admin/common/countries/%s/states", params.Country) + uri := fmt.Sprintf("/identity-management/v3/user-admin/common/countries/%s/states", params.Country) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListStates, err) } - var rval []string - resp, err := i.Exec(req, &rval) + var result []string + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrListStates, err) } @@ -147,22 +169,22 @@ func (i *iam) ListStates(ctx context.Context, params ListStatesRequest) ([]strin return nil, fmt.Errorf("%s: %w", ErrListStates, i.Error(resp)) } - return rval, nil + return result, nil } func (i *iam) ListTimeoutPolicies(ctx context.Context) ([]TimeoutPolicy, error) { logger := i.Log(ctx) logger.Debug("ListTimeoutPolicies") - getURL := "/identity-management/v2/user-admin/common/timeout-policies" + uri := "/identity-management/v3/user-admin/common/timeout-policies" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrListTimeoutPolicies, err) } - var rval []TimeoutPolicy - resp, err := i.Exec(req, &rval) + var result []TimeoutPolicy + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrListTimeoutPolicies, err) } @@ -171,22 +193,59 @@ func (i *iam) ListTimeoutPolicies(ctx context.Context) ([]TimeoutPolicy, error) return nil, fmt.Errorf("%s: %w", ErrListTimeoutPolicies, i.Error(resp)) } - return rval, nil + return result, nil +} + +func (i *iam) ListAccountSwitchKeys(ctx context.Context, params ListAccountSwitchKeysRequest) (ListAccountSwitchKeysResponse, error) { + logger := i.Log(ctx) + logger.Debug("ListAccountSwitchKeys") + + if params.ClientID == "" { + params.ClientID = "self" + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/api-clients/%s/account-switch-keys", params.ClientID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListAccountSwitchKeys, err) + } + + if params.Search != "" { + q := uri.Query() + q.Add("search", params.Search) + uri.RawQuery = q.Encode() + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrListAccountSwitchKeys, err) + } + + var result ListAccountSwitchKeysResponse + resp, err := i.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrListAccountSwitchKeys, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrListAccountSwitchKeys, i.Error(resp)) + } + + return result, nil } func (i *iam) SupportedContactTypes(ctx context.Context) ([]string, error) { logger := i.Log(ctx) logger.Debug("SupportedContactTypes") - getURL := "/identity-management/v2/user-admin/common/contact-types" + uri := "/identity-management/v3/user-admin/common/contact-types" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrSupportedContactTypes, err) } - var rval []string - resp, err := i.Exec(req, &rval) + var result []string + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrSupportedContactTypes, err) } @@ -195,22 +254,22 @@ func (i *iam) SupportedContactTypes(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("%s: %w", ErrSupportedContactTypes, i.Error(resp)) } - return rval, nil + return result, nil } func (i *iam) SupportedCountries(ctx context.Context) ([]string, error) { logger := i.Log(ctx) logger.Debug("SupportedCountries") - getURL := "/identity-management/v2/user-admin/common/countries" + uri := "/identity-management/v3/user-admin/common/countries" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrSupportedCountries, err) } - var rval []string - resp, err := i.Exec(req, &rval) + var result []string + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrSupportedCountries, err) } @@ -219,22 +278,22 @@ func (i *iam) SupportedCountries(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("%s: %w", ErrSupportedCountries, i.Error(resp)) } - return rval, nil + return result, nil } func (i *iam) SupportedLanguages(ctx context.Context) ([]string, error) { logger := i.Log(ctx) logger.Debug("SupportedLanguages") - getURL := "/identity-management/v2/user-admin/common/supported-languages" + uri := "/identity-management/v3/user-admin/common/supported-languages" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrSupportedLanguages, err) } - var rval []string - resp, err := i.Exec(req, &rval) + var result []string + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrSupportedLanguages, err) } @@ -243,22 +302,22 @@ func (i *iam) SupportedLanguages(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("%s: %w", ErrSupportedLanguages, i.Error(resp)) } - return rval, nil + return result, nil } func (i *iam) SupportedTimezones(ctx context.Context) ([]Timezone, error) { logger := i.Log(ctx) logger.Debug("SupportedTimezones") - getURL := "/identity-management/v2/user-admin/common/timezones" + uri := "/identity-management/v3/user-admin/common/timezones" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrSupportedTimezones, err) } - var rval []Timezone - resp, err := i.Exec(req, &rval) + var result []Timezone + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrSupportedTimezones, err) } @@ -267,5 +326,5 @@ func (i *iam) SupportedTimezones(ctx context.Context) ([]Timezone, error) { return nil, fmt.Errorf("%s: %w", ErrSupportedTimezones, i.Error(resp)) } - return rval, nil + return result, nil } diff --git a/pkg/iam/support_test.go b/pkg/iam/support_test.go index b03d807e..75cbb54f 100644 --- a/pkg/iam/support_test.go +++ b/pkg/iam/support_test.go @@ -11,6 +11,85 @@ import ( "github.com/tj/assert" ) +func TestIAM_GetPasswordPolicy(t *testing.T) { + tests := map[string]struct { + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetPasswordPolicyResponse + withError func(*testing.T, error) + }{ + "200 OK": { + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/user-admin/common/password-policy", + responseBody: ` +{ + "caseDif": 0, + "maxRepeating": 1, + "minDigits": 1, + "minLength": 1, + "minLetters": 1, + "minNonAlpha": 0, + "minReuse": 1, + "pwclass": "test_class", + "rotateFrequency": 10 +} +`, + expectedResponse: &GetPasswordPolicyResponse{ + CaseDiff: 0, + MaxRepeating: 1, + MinDigits: 1, + MinLength: 1, + MinLetters: 1, + MinNonAlpha: 0, + MinReuse: 1, + PwClass: "test_class", + RotateFrequency: 10, + }, + }, + "500 internal server error": { + responseStatus: http.StatusInternalServerError, + expectedPath: "/identity-management/v3/user-admin/common/password-policy", + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +}`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetPasswordPolicy(context.Background()) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + func TestIAM_SupportedCountries(t *testing.T) { tests := map[string]struct { responseStatus int @@ -27,7 +106,7 @@ func TestIAM_SupportedCountries(t *testing.T) { "Greenland", "Grenada" ]`, - expectedPath: "/identity-management/v2/user-admin/common/countries", + expectedPath: "/identity-management/v3/user-admin/common/countries", expectedResponse: []string{"Greece", "Greenland", "Grenada"}, }, "500 internal server error": { @@ -39,7 +118,7 @@ func TestIAM_SupportedCountries(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/countries", + expectedPath: "/identity-management/v3/user-admin/common/countries", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -52,23 +131,23 @@ func TestIAM_SupportedCountries(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.SupportedCountries(context.Background()) - if test.withError != nil { - test.withError(t, err) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -92,7 +171,7 @@ func TestIAM_SupportedTimezones(t *testing.T) { "posix": "Asia/Rangoon" } ]`, - expectedPath: "/identity-management/v2/user-admin/common/timezones", + expectedPath: "/identity-management/v3/user-admin/common/timezones", expectedResponse: []Timezone{ { Timezone: "Asia/Rangoon", @@ -111,7 +190,7 @@ func TestIAM_SupportedTimezones(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/timezones", + expectedPath: "/identity-management/v3/user-admin/common/timezones", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -124,23 +203,23 @@ func TestIAM_SupportedTimezones(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.SupportedTimezones(context.Background()) - if test.withError != nil { - test.withError(t, err) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -160,7 +239,7 @@ func TestIAM_SupportedContactTypes(t *testing.T) { "Billing", "Security" ]`, - expectedPath: "/identity-management/v2/user-admin/common/contact-types", + expectedPath: "/identity-management/v3/user-admin/common/contact-types", expectedResponse: []string{"Billing", "Security"}, }, "500 internal server error": { @@ -172,7 +251,7 @@ func TestIAM_SupportedContactTypes(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/contact-types", + expectedPath: "/identity-management/v3/user-admin/common/contact-types", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -185,23 +264,23 @@ func TestIAM_SupportedContactTypes(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.SupportedContactTypes(context.Background()) - if test.withError != nil { - test.withError(t, err) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -221,7 +300,7 @@ func TestIAM_SupportedLanguages(t *testing.T) { "Deutsch", "English" ]`, - expectedPath: "/identity-management/v2/user-admin/common/supported-languages", + expectedPath: "/identity-management/v3/user-admin/common/supported-languages", expectedResponse: []string{"Deutsch", "English"}, }, "500 internal server error": { @@ -233,7 +312,7 @@ func TestIAM_SupportedLanguages(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/supported-languages", + expectedPath: "/identity-management/v3/user-admin/common/supported-languages", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -246,23 +325,23 @@ func TestIAM_SupportedLanguages(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.SupportedLanguages(context.Background()) - if test.withError != nil { - test.withError(t, err) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -282,7 +361,7 @@ func TestIAM_ListProducts(t *testing.T) { "EdgeComputing for Java", "Streaming" ]`, - expectedPath: "/identity-management/v2/user-admin/common/notification-products", + expectedPath: "/identity-management/v3/user-admin/common/notification-products", expectedResponse: []string{"EdgeComputing for Java", "Streaming"}, }, "500 internal server error": { @@ -294,7 +373,7 @@ func TestIAM_ListProducts(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/notification-products", + expectedPath: "/identity-management/v3/user-admin/common/notification-products", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -307,23 +386,23 @@ func TestIAM_ListProducts(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.ListProducts(context.Background()) - if test.withError != nil { - test.withError(t, err) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -349,7 +428,7 @@ func TestIAM_ListTimeoutPolicies(t *testing.T) { "value": 1800 } ]`, - expectedPath: "/identity-management/v2/user-admin/common/timeout-policies", + expectedPath: "/identity-management/v3/user-admin/common/timeout-policies", expectedResponse: []TimeoutPolicy{ { Name: "after15Minutes", @@ -370,7 +449,7 @@ func TestIAM_ListTimeoutPolicies(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/timeout-policies", + expectedPath: "/identity-management/v3/user-admin/common/timeout-policies", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -383,23 +462,23 @@ func TestIAM_ListTimeoutPolicies(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) result, err := client.ListTimeoutPolicies(context.Background()) - if test.withError != nil { - test.withError(t, err) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -423,7 +502,7 @@ func TestIAM_ListStates(t *testing.T) { "AB", "BC" ]`, - expectedPath: "/identity-management/v2/user-admin/common/countries/canada/states", + expectedPath: "/identity-management/v3/user-admin/common/countries/canada/states", expectedResponse: []string{"AB", "BC"}, }, "500 internal server error": { @@ -438,7 +517,7 @@ func TestIAM_ListStates(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/common/countries/canada/states", + expectedPath: "/identity-management/v3/user-admin/common/countries/canada/states", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -458,23 +537,213 @@ func TestIAM_ListStates(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.ListStates(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.ListStates(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) + }) + } +} + +func TestIAM_ListAccountSwitchKeys(t *testing.T) { + tests := map[string]struct { + params ListAccountSwitchKeysRequest + responseStatus int + expectedPath string + responseBody string + expectedResponse ListAccountSwitchKeysResponse + withError func(*testing.T, error) + }{ + "200 OK with specified client": { + params: ListAccountSwitchKeysRequest{ + ClientID: "test1234", + }, + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/api-clients/test1234/account-switch-keys", + responseBody: ` +[ + { + "accountName": "Test Name A", + "accountSwitchKey": "ABC-123" + }, + { + "accountName": "Test Name A", + "accountSwitchKey": "ABCD-1234" + }, + { + "accountName": "Test Name B", + "accountSwitchKey": "ABCDE-12345" + } +] +`, + expectedResponse: ListAccountSwitchKeysResponse{ + AccountSwitchKey{ + AccountName: "Test Name A", + AccountSwitchKey: "ABC-123", + }, + AccountSwitchKey{ + AccountName: "Test Name A", + AccountSwitchKey: "ABCD-1234", + }, + AccountSwitchKey{ + AccountName: "Test Name B", + AccountSwitchKey: "ABCDE-12345", + }, + }, + }, + "200 OK without specified client": { + params: ListAccountSwitchKeysRequest{}, + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/api-clients/self/account-switch-keys", + responseBody: ` +[ + { + "accountName": "Test Name A", + "accountSwitchKey": "ABC-123" + }, + { + "accountName": "Test Name A", + "accountSwitchKey": "ABCD-1234" + }, + { + "accountName": "Test Name B", + "accountSwitchKey": "ABCDE-12345" + } +] +`, + expectedResponse: ListAccountSwitchKeysResponse{ + AccountSwitchKey{ + AccountName: "Test Name A", + AccountSwitchKey: "ABC-123", + }, + AccountSwitchKey{ + AccountName: "Test Name A", + AccountSwitchKey: "ABCD-1234", + }, + AccountSwitchKey{ + AccountName: "Test Name B", + AccountSwitchKey: "ABCDE-12345", + }, + }, + }, + "200 OK - no account switch keys": { + params: ListAccountSwitchKeysRequest{ + ClientID: "test1234", + }, + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/api-clients/test1234/account-switch-keys", + responseBody: `[]`, + expectedResponse: ListAccountSwitchKeysResponse{}, + }, + "200 OK with query param": { + params: ListAccountSwitchKeysRequest{ + ClientID: "test1234", + Search: "Name A", + }, + responseStatus: http.StatusOK, + expectedPath: "/identity-management/v3/api-clients/test1234/account-switch-keys?search=Name+A", + responseBody: ` +[ + { + "accountName": "Test Name A", + "accountSwitchKey": "ABC-123" + }, + { + "accountName": "Test Name A", + "accountSwitchKey": "ABCD-1234" + } +] +`, + expectedResponse: ListAccountSwitchKeysResponse{ + AccountSwitchKey{ + AccountName: "Test Name A", + AccountSwitchKey: "ABC-123", + }, + AccountSwitchKey{ + AccountName: "Test Name A", + AccountSwitchKey: "ABCD-1234", + }, + }, + }, + "404 not found": { + params: ListAccountSwitchKeysRequest{ + ClientID: "test12344", + }, + responseStatus: http.StatusNotFound, + expectedPath: "/identity-management/v3/api-clients/test12344/account-switch-keys", + responseBody: ` +{ + "instances": "", + "type": "/identity-management/error-types/2", + "status": 404, + "title": "invalid open identity", + "detail": "" +} +`, + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "/identity-management/error-types/2", + Title: "invalid open identity", + StatusCode: http.StatusNotFound, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + params: ListAccountSwitchKeysRequest{ + ClientID: "test12344", + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +}`, + expectedPath: "/identity-management/v3/api-clients/test12344/account-switch-keys", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + users, err := client.ListAccountSwitchKeys(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expectedResponse, users) }) } } diff --git a/pkg/iam/user.go b/pkg/iam/user.go index 77976d94..9667659b 100644 --- a/pkg/iam/user.go +++ b/pkg/iam/user.go @@ -6,73 +6,31 @@ import ( "fmt" "net/http" "net/url" - "path" "strconv" + "time" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" ) type ( - // Users is the IAM user identity API interface - Users interface { - // CreateUser creates a user in the account specified in your own API client credentials or clone an existing user's role assignments - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-ui-identity - CreateUser(context.Context, CreateUserRequest) (*User, error) - - // GetUser gets a specific user's profile - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-ui-identity - GetUser(context.Context, GetUserRequest) (*User, error) - - // ListUsers returns a list of users who have access on this account - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/get-ui-identities - ListUsers(context.Context, ListUsersRequest) ([]UserListItem, error) - - // RemoveUser removes a user identity - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/delete-ui-identity - RemoveUser(context.Context, RemoveUserRequest) error - - // UpdateUserAuthGrants edits what groups a user has access to, and how the user can interact with the objects in those groups - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-ui-uiidentity-auth-grants - UpdateUserAuthGrants(context.Context, UpdateUserAuthGrantsRequest) ([]AuthGrant, error) - - // UpdateUserInfo updates a user's information - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-ui-identity-basic-info - UpdateUserInfo(context.Context, UpdateUserInfoRequest) (*UserBasicInfo, error) - - // UpdateUserNotifications subscribes or un-subscribe user to product notification emails - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-notifications - UpdateUserNotifications(context.Context, UpdateUserNotificationsRequest) (*UserNotifications, error) - - // UpdateTFA updates a user's two-factor authentication setting and can reset tfa - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/put-ui-identity-tfa - UpdateTFA(context.Context, UpdateTFARequest) error - } - - // CreateUserRequest contains the request parameters for the create user endpoint + // CreateUserRequest contains the request parameters for the CreateUser endpoint. CreateUserRequest struct { UserBasicInfo AuthGrants []AuthGrantRequest `json:"authGrants,omitempty"` - Notifications UserNotifications `json:"notifications,omitempty"` + Notifications *UserNotifications `json:"notifications,omitempty"` SendEmail bool `json:"-"` } - // ListUsersRequest contains the request parameters for the list users endpoint + // ListUsersRequest contains the request parameters for the ListUsers endpoint. ListUsersRequest struct { GroupID *int64 AuthGrants bool Actions bool } - // GetUserRequest contains the request parameters of the get user endpoint + // GetUserRequest contains the request parameters for the GetUser endpoint. GetUserRequest struct { IdentityID string Actions bool @@ -80,93 +38,104 @@ type ( Notifications bool } - // UpdateUserInfoRequest contains the request parameters of the update user endpoint + // UpdateUserInfoRequest contains the request parameters for the UpdateUser endpoint. UpdateUserInfoRequest struct { IdentityID string User UserBasicInfo } - // UpdateUserNotificationsRequest contains the request parameters of the update user notifications endpoint + // UpdateUserNotificationsRequest contains the request parameters for the UpdateUserNotifications endpoint. UpdateUserNotificationsRequest struct { IdentityID string - Notifications UserNotifications + Notifications *UserNotifications } - // UpdateUserAuthGrantsRequest contains the request parameters of the update user auth grants endpoint + // UpdateUserAuthGrantsRequest contains the request parameters for the UpdateUserAuthGrants endpoint. UpdateUserAuthGrantsRequest struct { IdentityID string AuthGrants []AuthGrantRequest } - // RemoveUserRequest contains the request parameters of the remove user endpoint + // RemoveUserRequest contains the request parameters of the RemoveUser endpoint. RemoveUserRequest struct { IdentityID string } - // User describes the response of the get and create user endpoints + // User describes the response from the GetUser and CreateUser endpoints. User struct { UserBasicInfo - IdentityID string `json:"uiIdentityId"` - IsLocked bool `json:"isLocked"` - LastLoginDate string `json:"lastLoginDate,omitempty"` - PasswordExpiryDate string `json:"passwordExpiryDate,omitempty"` - TFAConfigured bool `json:"tfaConfigured"` - EmailUpdatePending bool `json:"emailUpdatePending"` - AuthGrants []AuthGrant `json:"authGrants,omitempty"` - Notifications UserNotifications `json:"notifications,omitempty"` - } - - // UserListItem describes the response of the list endpoint + IdentityID string `json:"uiIdentityId"` + IsLocked bool `json:"isLocked"` + LastLoginDate time.Time `json:"lastLoginDate,omitempty"` + PasswordExpiryDate time.Time `json:"passwordExpiryDate,omitempty"` + TFAConfigured bool `json:"tfaConfigured"` + EmailUpdatePending bool `json:"emailUpdatePending"` + AuthGrants []AuthGrant `json:"authGrants,omitempty"` + Notifications UserNotifications `json:"notifications,omitempty"` + Actions *UserActions `json:"actions,omitempty"` + UserStatus string `json:"userStatus"` + AccountID string `json:"accountId"` + AdditionalAuthenticationConfigured bool `json:"additionalAuthenticationConfigured"` + } + + // UserListItem describes the response from the ListUsers endpoint. UserListItem struct { - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - UserName string `json:"uiUserName,omitempty"` - Email string `json:"email"` - TFAEnabled bool `json:"tfaEnabled"` - IdentityID string `json:"uiIdentityId"` - IsLocked bool `json:"isLocked"` - LastLoginDate string `json:"lastLoginDate,omitempty"` - TFAConfigured bool `json:"tfaConfigured"` - AccountID string `json:"accountId"` - Actions *UserActions `json:"actions,omitempty"` - AuthGrants []AuthGrant `json:"authGrants,omitempty"` - } - - // UserBasicInfo is the user basic info structure + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + UserName string `json:"uiUserName,omitempty"` + Email string `json:"email"` + TFAEnabled bool `json:"tfaEnabled"` + IdentityID string `json:"uiIdentityId"` + IsLocked bool `json:"isLocked"` + LastLoginDate time.Time `json:"lastLoginDate,omitempty"` + TFAConfigured bool `json:"tfaConfigured"` + AccountID string `json:"accountId"` + Actions *UserActions `json:"actions,omitempty"` + AuthGrants []AuthGrant `json:"authGrants,omitempty"` + AdditionalAuthentication Authentication `json:"additionalAuthentication"` + AdditionalAuthenticationConfigured bool `json:"additionalAuthenticationConfigured"` + } + + // UserBasicInfo is the user basic info structure. UserBasicInfo struct { - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - UserName string `json:"uiUserName,omitempty"` - Email string `json:"email"` - Phone string `json:"phone,omitempty"` - TimeZone string `json:"timeZone,omitempty"` - JobTitle string `json:"jobTitle"` - TFAEnabled bool `json:"tfaEnabled"` - SecondaryEmail string `json:"secondaryEmail,omitempty"` - MobilePhone string `json:"mobilePhone,omitempty"` - Address string `json:"address,omitempty"` - City string `json:"city,omitempty"` - State string `json:"state,omitempty"` - ZipCode string `json:"zipCode,omitempty"` - Country string `json:"country"` - ContactType string `json:"contactType,omitempty"` - PreferredLanguage string `json:"preferredLanguage,omitempty"` - SessionTimeOut *int `json:"sessionTimeOut,omitempty"` - } - - // UserActions encapsulates permissions available to the user for this group + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + UserName string `json:"uiUserName,omitempty"` + Email string `json:"email"` + Phone string `json:"phone,omitempty"` + TimeZone string `json:"timeZone,omitempty"` + JobTitle string `json:"jobTitle"` + TFAEnabled bool `json:"tfaEnabled"` + SecondaryEmail string `json:"secondaryEmail,omitempty"` + MobilePhone string `json:"mobilePhone,omitempty"` + Address string `json:"address,omitempty"` + City string `json:"city,omitempty"` + State string `json:"state,omitempty"` + ZipCode string `json:"zipCode,omitempty"` + Country string `json:"country"` + ContactType string `json:"contactType,omitempty"` + PreferredLanguage string `json:"preferredLanguage,omitempty"` + SessionTimeOut *int `json:"sessionTimeOut,omitempty"` + AdditionalAuthentication Authentication `json:"additionalAuthentication"` + } + + // UserActions encapsulates permissions available to the user for this group. UserActions struct { - APIClient bool `json:"apiClient"` - Delete bool `json:"delete"` - Edit bool `json:"edit"` - IsCloneable bool `json:"isCloneable"` - ResetPassword bool `json:"resetPassword"` - ThirdPartyAccess bool `json:"thirdPartyAccess"` - CanEditTFA bool `json:"canEditTFA"` - EditProfile bool `json:"editProfile"` - } - - // AuthGrant is user’s role assignments, per group + APIClient bool `json:"apiClient"` + Delete bool `json:"delete"` + Edit bool `json:"edit"` + IsCloneable bool `json:"isCloneable"` + ResetPassword bool `json:"resetPassword"` + ThirdPartyAccess bool `json:"thirdPartyAccess"` + CanEditTFA bool `json:"canEditTFA"` + CanEditMFA bool `json:"canEditMFA"` + CanEditNone bool `json:"canEditNone"` + EditProfile bool `json:"editProfile"` + EditRole bool `json:"editRole"` + CanGenerateBypassCode bool `json:"canGenerateBypassCode"` + } + + // AuthGrant is user’s role assignments, per group. AuthGrant struct { GroupID int64 `json:"groupId"` GroupName string `json:"groupName"` @@ -177,7 +146,7 @@ type ( Subgroups []AuthGrant `json:"subGroups,omitempty"` } - // AuthGrantRequest is user’s role assignments, per group for the create/update operation + // AuthGrantRequest is user’s role assignments, per group for the create/update operation. AuthGrantRequest struct { GroupID int64 `json:"groupId"` IsBlocked bool `json:"isBlocked"` @@ -185,152 +154,199 @@ type ( Subgroups []AuthGrantRequest `json:"subGroups,omitempty"` } - // UserNotifications types of notification emails the user receives + // UserNotifications types of notification emails the user receives. UserNotifications struct { EnableEmail bool `json:"enableEmailNotifications"` Options UserNotificationOptions `json:"options"` } - // UserNotificationOptions types of notification emails the user receives + // UserNotificationOptions types of notification emails the user receives. UserNotificationOptions struct { - NewUser bool `json:"newUserNotification"` - PasswordExpiry bool `json:"passwordExpiry"` - Proactive []string `json:"proactive"` - Upgrade []string `json:"upgrade"` + NewUser bool `json:"newUserNotification"` + PasswordExpiry bool `json:"passwordExpiry"` + Proactive []string `json:"proactive"` + Upgrade []string `json:"upgrade"` + APIClientCredentialExpiry bool `json:"apiClientCredentialExpiryNotification"` } - // TFAActionType is a type for tfa action constants + // TFAActionType is a type for tfa action constants. TFAActionType string - // UpdateTFARequest contains the request parameters of the tfa user endpoint + // UpdateTFARequest contains the request parameters for the UpdateTFA endpoint. UpdateTFARequest struct { IdentityID string Action TFAActionType } + + // Authentication is a type of additional authentication. + Authentication string + + // UpdateMFARequest contains the request body for the UpdateMFA endpoint. + UpdateMFARequest struct { + IdentityID string + Value Authentication + } + + // ResetMFARequest contains the request parameters for the ResetMFA endpoint. + ResetMFARequest struct { + IdentityID string + } ) const ( - // TFAActionEnable ia an action value to use to enable tfa + // TFAActionEnable is an action value to use to enable tfa. TFAActionEnable TFAActionType = "enable" - // TFAActionDisable ia an action value to use to disable tfa + // TFAActionDisable is an action value to use to disable tfa. TFAActionDisable TFAActionType = "disable" - // TFAActionReset ia an action value to use to reset tfa + // TFAActionReset is an action value to use to reset tfa. TFAActionReset TFAActionType = "reset" + // MFAAuthentication is authentication of type MFA. + MFAAuthentication Authentication = "MFA" + // TFAAuthentication is authentication of type TFA. + TFAAuthentication Authentication = "TFA" + // NoneAuthentication represents a state where no authentication method is configured. + NoneAuthentication Authentication = "NONE" ) var ( - // ErrCreateUser is returned when CreateUser fails + // ErrCreateUser is returned when CreateUser fails. ErrCreateUser = errors.New("create user") - // ErrGetUser is returned when GetUser fails + // ErrGetUser is returned when GetUser fails. ErrGetUser = errors.New("get user") - // ErrListUsers is returned when GetUser fails + // ErrListUsers is returned when ListUsers fails. ErrListUsers = errors.New("list users") - // ErrRemoveUser is returned when RemoveUser fails + // ErrRemoveUser is returned when RemoveUser fails. ErrRemoveUser = errors.New("remove user") - // ErrUpdateUserAuthGrants is returned when UpdateUserAuthGrants fails + // ErrUpdateUserAuthGrants is returned when UpdateUserAuthGrants fails. ErrUpdateUserAuthGrants = errors.New("update user auth grants") - // ErrUpdateUserInfo is returned when UpdateUserInfo fails + // ErrUpdateUserInfo is returned when UpdateUserInfo fails. ErrUpdateUserInfo = errors.New("update user info") - // ErrUpdateUserNotifications is returned when UpdateUserNotifications fails + // ErrUpdateUserNotifications is returned when UpdateUserNotifications fails. ErrUpdateUserNotifications = errors.New("update user notifications") - // ErrUpdateTFA is returned when UpdateTFA fails + // ErrUpdateTFA is returned when UpdateTFA fails. ErrUpdateTFA = errors.New("update user's two-factor authentication") + + // ErrUpdateMFA is returned when UpdateMFA fails. + ErrUpdateMFA = errors.New("update user's authentication method") + + // ErrResetMFA is returned when ResetMFA fails. + ErrResetMFA = errors.New("reset user's authentication method") ) -// Validate performs validation on AuthGrant +// Validate validates Authentication. +func (a Authentication) Validate() error { + return validation.In(MFAAuthentication, TFAAuthentication, NoneAuthentication). + Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s' or '%s'", + a, MFAAuthentication, TFAAuthentication, NoneAuthentication)). + Validate(a) +} + +// Validate validates validation on AuthGrant. func (r AuthGrant) Validate() error { - return validation.Errors{ - "group_id": validation.Validate(r.GroupID, validation.Required), - "role_id": validation.Validate(r.RoleID, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "GroupID": validation.Validate(r.GroupID, validation.Required), + "RoleID": validation.Validate(r.RoleID, validation.Required), + }) } -// Validate validates CreateUserRequest +// Validate validates CreateUserRequest. func (r CreateUserRequest) Validate() error { - return validation.Errors{ - "country": validation.Validate(r.Country, validation.Required), - "email": validation.Validate(r.Email, validation.Required, is.EmailFormat), - "firstName": validation.Validate(r.FirstName, validation.Required), - "lastName": validation.Validate(r.LastName, validation.Required), - "authGrants": validation.Validate(r.AuthGrants, validation.Required), - "notifications": validation.Validate(r.Notifications, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Country": validation.Validate(r.Country, validation.Required), + "Email": validation.Validate(r.Email, validation.Required, is.EmailFormat), + "FirstName": validation.Validate(r.FirstName, validation.Required), + "LastName": validation.Validate(r.LastName, validation.Required), + "AuthGrants": validation.Validate(r.AuthGrants, validation.Required), + "Notifications": validation.Validate(r.Notifications), + "AdditionalAuthentication": validation.Validate(r.AdditionalAuthentication, validation.Required), + }) } -// Validate validates GetUserRequest +// Validate validates GetUserRequest. func (r GetUserRequest) Validate() error { - return validation.Errors{ - "uiIdentity": validation.Validate(r.IdentityID, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "IdentityID": validation.Validate(r.IdentityID, validation.Required), + }) } -// Validate validates UpdateUserInfoRequest +// Validate validates UpdateUserInfoRequest. func (r UpdateUserInfoRequest) Validate() error { - return validation.Errors{ - "uiIdentity": validation.Validate(r.IdentityID, validation.Required), - "firstName": validation.Validate(r.User.FirstName, validation.Required), - "lastName": validation.Validate(r.User.LastName, validation.Required), - "country": validation.Validate(r.User.Country, validation.Required), - "timeZone": validation.Validate(r.User.TimeZone, validation.Required), - "preferredLanguage": validation.Validate(r.User.PreferredLanguage, validation.Required), - "sessionTimeOut": validation.Validate(r.User.SessionTimeOut, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "IdentityID": validation.Validate(r.IdentityID, validation.Required), + "FirstName": validation.Validate(r.User.FirstName, validation.Required), + "LastName": validation.Validate(r.User.LastName, validation.Required), + "Country": validation.Validate(r.User.Country, validation.Required), + "TimeZone": validation.Validate(r.User.TimeZone, validation.Required), + "PreferredLanguage": validation.Validate(r.User.PreferredLanguage, validation.Required), + "SessionTimeOut": validation.Validate(r.User.SessionTimeOut, validation.Required), + }) } -// Validate validates UpdateUserNotificationsRequest +// Validate validates UpdateUserNotificationsRequest. func (r UpdateUserNotificationsRequest) Validate() error { - return validation.Errors{ - "uiIdentity": validation.Validate(r.IdentityID, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "IdentityID": validation.Validate(r.IdentityID, validation.Required), + "Notifications": validation.Validate(r.Notifications, validation.Required), + }) } -// Validate validates UpdateUserAuthGrantsRequest +// Validate validates UpdateUserAuthGrantsRequest. func (r UpdateUserAuthGrantsRequest) Validate() error { - return validation.Errors{ - "uiIdentity": validation.Validate(r.IdentityID, validation.Required), - "authGrants": validation.Validate(r.AuthGrants, validation.Required), - }.Filter() + return edgegriderr.ParseValidationErrors(validation.Errors{ + "IdentityID": validation.Validate(r.IdentityID, validation.Required), + "AuthGrants": validation.Validate(r.AuthGrants, validation.Required), + }) } -// Validate validates RemoveUserRequest +// Validate validates RemoveUserRequest. func (r RemoveUserRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "uiIdentity": validation.Validate(r.IdentityID, validation.Required), - }.Filter() + }) } -// Validate validates UpdateTFARequest +// Validate validates UpdateTFARequest. func (r UpdateTFARequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), "Action": validation.Validate(r.Action, validation.Required, validation.In(TFAActionEnable, TFAActionDisable, TFAActionReset). Error(fmt.Sprintf("value '%s' is invalid. Must be one of: 'enable', 'disable' or 'reset'", r.Action))), - }.Filter() + }) +} + +// Validate validates UpdateMFARequest. +func (r UpdateMFARequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Value": validation.Validate(r.Value, validation.Required), + }) } func (i *iam) CreateUser(ctx context.Context, params CreateUserRequest) (*User, error) { + logger := i.Log(ctx) + logger.Debug("CreateUser") + if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w:\n%s", ErrCreateUser, ErrStructValidation, err) } - u, err := url.Parse(path.Join("/identity-management/v2/user-admin", "ui-identities")) + uri, err := url.Parse("/identity-management/v3/user-admin/ui-identities") if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateUser, err) } - q := u.Query() + q := uri.Query() q.Add("sendEmail", strconv.FormatBool(params.SendEmail)) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateUser, err) } @@ -349,29 +365,32 @@ func (i *iam) CreateUser(ctx context.Context, params CreateUserRequest) (*User, } func (i *iam) GetUser(ctx context.Context, params GetUserRequest) (*User, error) { + logger := i.Log(ctx) + logger.Debug("GetUser") + if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w:\n%s", ErrGetUser, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s", params.IdentityID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetUser, err) } - q := u.Query() + q := uri.Query() q.Add("actions", strconv.FormatBool(params.Actions)) q.Add("authGrants", strconv.FormatBool(params.AuthGrants)) q.Add("notifications", strconv.FormatBool(params.Notifications)) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetUser, err) } - var rval User - resp, err := i.Exec(req, &rval) + var result User + resp, err := i.Exec(req, &result) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrGetUser, err) } @@ -380,24 +399,27 @@ func (i *iam) GetUser(ctx context.Context, params GetUserRequest) (*User, error) return nil, fmt.Errorf("%s: %w", ErrGetUser, i.Error(resp)) } - return &rval, nil + return &result, nil } func (i *iam) ListUsers(ctx context.Context, params ListUsersRequest) ([]UserListItem, error) { - u, err := url.Parse("/identity-management/v2/user-admin/ui-identities") + logger := i.Log(ctx) + logger.Debug("ListUsers") + + uri, err := url.Parse("/identity-management/v3/user-admin/ui-identities") if err != nil { return nil, fmt.Errorf("%w: failed to parse the URL:\n%s", ErrListUsers, err) } - q := u.Query() + q := uri.Query() q.Add("actions", strconv.FormatBool(params.Actions)) q.Add("authGrants", strconv.FormatBool(params.AuthGrants)) if params.GroupID != nil { - q.Add("groupId", strconv.FormatInt(int64(*params.GroupID), 10)) + q.Add("groupId", strconv.FormatInt(*params.GroupID, 10)) } - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request:\n%s", ErrListUsers, err) } @@ -416,16 +438,19 @@ func (i *iam) ListUsers(ctx context.Context, params ListUsersRequest) ([]UserLis } func (i *iam) RemoveUser(ctx context.Context, params RemoveUserRequest) error { + logger := i.Log(ctx) + logger.Debug("RemoveUser") + if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w:\n%s", ErrRemoveUser, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s", params.IdentityID)) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrRemoveUser, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrRemoveUser, err) } @@ -443,23 +468,26 @@ func (i *iam) RemoveUser(ctx context.Context, params RemoveUserRequest) error { } func (i *iam) UpdateUserAuthGrants(ctx context.Context, params UpdateUserAuthGrantsRequest) ([]AuthGrant, error) { + logger := i.Log(ctx) + logger.Debug("UpdateUserAuthGrants") + if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateUserAuthGrants, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/auth-grants", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/auth-grants", params.IdentityID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateUserAuthGrants, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateUserAuthGrants, err) } - rval := make([]AuthGrant, 0) + var result []AuthGrant - resp, err := i.Exec(req, &rval, params.AuthGrants) + resp, err := i.Exec(req, &result, params.AuthGrants) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateUserAuthGrants, err) } @@ -468,26 +496,29 @@ func (i *iam) UpdateUserAuthGrants(ctx context.Context, params UpdateUserAuthGra return nil, fmt.Errorf("%s: %w", ErrUpdateUserAuthGrants, i.Error(resp)) } - return rval, nil + return result, nil } func (i *iam) UpdateUserInfo(ctx context.Context, params UpdateUserInfoRequest) (*UserBasicInfo, error) { + logger := i.Log(ctx) + logger.Debug("UpdateUserInfo") + if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateUserInfo, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/basic-info", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/basic-info", params.IdentityID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateUserInfo, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateUserInfo, err) } - var rval UserBasicInfo - resp, err := i.Exec(req, &rval, params.User) + var result UserBasicInfo + resp, err := i.Exec(req, &result, params.User) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateUserInfo, err) } @@ -496,26 +527,29 @@ func (i *iam) UpdateUserInfo(ctx context.Context, params UpdateUserInfoRequest) return nil, fmt.Errorf("%s: %w", ErrUpdateUserInfo, i.Error(resp)) } - return &rval, nil + return &result, nil } func (i *iam) UpdateUserNotifications(ctx context.Context, params UpdateUserNotificationsRequest) (*UserNotifications, error) { + logger := i.Log(ctx) + logger.Debug("UpdateUserNotifications") + if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateUserNotifications, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/notifications", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/notifications", params.IdentityID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateUserNotifications, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateUserNotifications, err) } - var rval UserNotifications - resp, err := i.Exec(req, &rval, params.Notifications) + var result UserNotifications + resp, err := i.Exec(req, &result, params.Notifications) if err != nil { return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateUserNotifications, err) } @@ -524,24 +558,27 @@ func (i *iam) UpdateUserNotifications(ctx context.Context, params UpdateUserNoti return nil, fmt.Errorf("%s: %w", ErrUpdateUserNotifications, i.Error(resp)) } - return &rval, nil + return &result, nil } func (i *iam) UpdateTFA(ctx context.Context, params UpdateTFARequest) error { + logger := i.Log(ctx) + logger.Debug("UpdateTFA") + if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w:\n%s", ErrUpdateTFA, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/tfa", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/tfa", params.IdentityID)) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrUpdateTFA, err) } - q := u.Query() + q := uri.Query() q.Add("action", string(params.Action)) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrUpdateTFA, err) } @@ -557,3 +594,59 @@ func (i *iam) UpdateTFA(ctx context.Context, params UpdateTFARequest) error { return nil } + +func (i *iam) UpdateMFA(ctx context.Context, params UpdateMFARequest) error { + logger := i.Log(ctx) + logger.Debug("UpdateMFA") + + if err := params.Validate(); err != nil { + return fmt.Errorf("%s: %w:\n%s", ErrUpdateMFA, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/additionalAuthentication", params.IdentityID)) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrUpdateMFA, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrUpdateMFA, err) + } + + resp, err := i.Exec(req, nil, params.Value) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrUpdateMFA, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrUpdateMFA, i.Error(resp)) + } + + return nil +} + +func (i *iam) ResetMFA(ctx context.Context, params ResetMFARequest) error { + logger := i.Log(ctx) + logger.Debug("ResetMFA") + + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/additionalAuthentication/reset", params.IdentityID)) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrResetMFA, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil) + if err != nil { + return fmt.Errorf("%w: failed to create request: %s", ErrResetMFA, err) + } + + resp, err := i.Exec(req, nil, nil) + if err != nil { + return fmt.Errorf("%w: request failed: %s", ErrResetMFA, err) + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("%s: %w", ErrResetMFA, i.Error(resp)) + } + + return nil +} diff --git a/pkg/iam/user_lock.go b/pkg/iam/user_lock.go index 04a62201..c77d3eef 100644 --- a/pkg/iam/user_lock.go +++ b/pkg/iam/user_lock.go @@ -7,67 +7,58 @@ import ( "net/http" "net/url" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // UserLock is the IAM user lock/unlock API interface - UserLock interface { - // LockUser lock the user - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-ui-identity-lock - LockUser(context.Context, LockUserRequest) error - - // UnlockUser release the lock on a user's account - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-ui-identity-unlock - UnlockUser(context.Context, UnlockUserRequest) error - } - - // LockUserRequest contains the request parameters of the lock user endpoint + // LockUserRequest contains the request parameters for the LockUser endpoint. LockUserRequest struct { IdentityID string } - // UnlockUserRequest contains the request parameters of the unlock user endpoint + // UnlockUserRequest contains the request parameters for the UnlockUser endpoint. UnlockUserRequest struct { IdentityID string } ) var ( - // ErrLockUser is returned when LockUser fails + // ErrLockUser is returned when LockUser fails. ErrLockUser = errors.New("lock user") - // ErrUnlockUser is returned when UnlockUser fails + // ErrUnlockUser is returned when UnlockUser fails. ErrUnlockUser = errors.New("unlock user") ) -// Validate validates LockUserRequest +// Validate validates LockUserRequest. func (r LockUserRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), - }.Filter() + }) } -// Validate validates UnlockUserRequest +// Validate validates UnlockUserRequest. func (r UnlockUserRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), - }.Filter() + }) } func (i *iam) LockUser(ctx context.Context, params LockUserRequest) error { + logger := i.Log(ctx) + logger.Debug("LockUser") + if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w:\n%s", ErrLockUser, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/lock", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/lock", params.IdentityID)) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrLockUser, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrLockUser, err) } @@ -85,16 +76,19 @@ func (i *iam) LockUser(ctx context.Context, params LockUserRequest) error { } func (i *iam) UnlockUser(ctx context.Context, params UnlockUserRequest) error { + logger := i.Log(ctx) + logger.Debug("UnlockUser") + if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w:\n%s", ErrUnlockUser, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/unlock", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/unlock", params.IdentityID)) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrUnlockUser, err) } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrUnlockUser, err) } diff --git a/pkg/iam/user_lock_test.go b/pkg/iam/user_lock_test.go index 099a1244..66672593 100644 --- a/pkg/iam/user_lock_test.go +++ b/pkg/iam/user_lock_test.go @@ -25,7 +25,7 @@ func TestIAM_LockUser(t *testing.T) { }, responseStatus: http.StatusOK, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/lock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/lock", }, "204 No Content": { params: LockUserRequest{ @@ -33,7 +33,7 @@ func TestIAM_LockUser(t *testing.T) { }, responseStatus: http.StatusNoContent, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/lock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/lock", }, "404 Not Found": { params: LockUserRequest{ @@ -48,7 +48,7 @@ func TestIAM_LockUser(t *testing.T) { "title": "User not found", "type": "/useradmin-api/error-types/1100" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/X1-ABCDE/lock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/X1-ABCDE/lock", withError: func(t *testing.T, err error) { want := &Error{ Instance: "", @@ -73,7 +73,7 @@ func TestIAM_LockUser(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/lock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/lock", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -86,19 +86,19 @@ func TestIAM_LockUser(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.LockUser(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + err := client.LockUser(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) @@ -120,7 +120,7 @@ func TestIAM_UnlockUser(t *testing.T) { }, responseStatus: http.StatusOK, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/unlock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/unlock", }, "204 No Content": { params: UnlockUserRequest{ @@ -128,7 +128,7 @@ func TestIAM_UnlockUser(t *testing.T) { }, responseStatus: http.StatusNoContent, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/unlock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/unlock", }, "404 Not Found": { params: UnlockUserRequest{ @@ -143,7 +143,7 @@ func TestIAM_UnlockUser(t *testing.T) { "title": "User not found", "type": "/useradmin-api/error-types/1100" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/X1-ABCDE/unlock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/X1-ABCDE/unlock", withError: func(t *testing.T, err error) { want := &Error{ Instance: "", @@ -168,7 +168,7 @@ func TestIAM_UnlockUser(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/unlock", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/unlock", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -181,19 +181,19 @@ func TestIAM_UnlockUser(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.UnlockUser(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + err := client.UnlockUser(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) diff --git a/pkg/iam/user_password.go b/pkg/iam/user_password.go index a5f44633..459099e1 100644 --- a/pkg/iam/user_password.go +++ b/pkg/iam/user_password.go @@ -8,37 +8,23 @@ import ( "net/url" "strconv" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // UserPassword is the IAM managing user's password API interface - UserPassword interface { - // ResetUserPassword optionally sends a one-time use password to the user. - // If you send the email with the password directly to the user, the response for this operation doesn't include that password. - // If you don't send the password to the user through email, the password is included in the response. - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-reset-password - ResetUserPassword(context.Context, ResetUserPasswordRequest) (*ResetUserPasswordResponse, error) - - // SetUserPassword sets a specific password for a user - // - // See: https://techdocs.akamai.com/iam-user-admin/reference/post-set-password - SetUserPassword(context.Context, SetUserPasswordRequest) error - } - - // ResetUserPasswordRequest contains the request parameters of the reset user password endpoint + // ResetUserPasswordRequest contains the request parameters for the ResetUserPassword endpoint. ResetUserPasswordRequest struct { IdentityID string SendEmail bool } - // ResetUserPasswordResponse contains the response from the reset user password endpoint + // ResetUserPasswordResponse contains the response from the ResetUserPassword endpoint. ResetUserPasswordResponse struct { NewPassword string `json:"newPassword"` } - // SetUserPasswordRequest contains the request parameters of the set user password endpoint + // SetUserPasswordRequest contains the request parameters for the SetUserPassword endpoint. SetUserPasswordRequest struct { IdentityID string `json:"-"` NewPassword string `json:"newPassword"` @@ -46,43 +32,46 @@ type ( ) var ( - // ErrResetUserPassword is returned when ResetUserPassword fails + // ErrResetUserPassword is returned when ResetUserPassword fails. ErrResetUserPassword = errors.New("reset user password") - // ErrSetUserPassword is returned when SetUserPassword fails + // ErrSetUserPassword is returned when SetUserPassword fails. ErrSetUserPassword = errors.New("set user password") ) -// Validate validates ResetUserPasswordRequest +// Validate validates ResetUserPasswordRequest. func (r ResetUserPasswordRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), - }.Filter() + }) } -// Validate validates SetUserPasswordRequest +// Validate validates SetUserPasswordRequest. func (r SetUserPasswordRequest) Validate() error { - return validation.Errors{ + return edgegriderr.ParseValidationErrors(validation.Errors{ "IdentityID": validation.Validate(r.IdentityID, validation.Required), "NewPassword": validation.Validate(r.NewPassword, validation.Required), - }.Filter() + }) } func (i *iam) ResetUserPassword(ctx context.Context, params ResetUserPasswordRequest) (*ResetUserPasswordResponse, error) { + logger := i.Log(ctx) + logger.Debug("ResetUserPassword") + if err := params.Validate(); err != nil { return nil, fmt.Errorf("%s: %w:\n%s", ErrResetUserPassword, ErrStructValidation, err) } - u, err := url.Parse(fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/reset-password", params.IdentityID)) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/reset-password", params.IdentityID)) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrResetUserPassword, err) } - q := u.Query() + q := uri.Query() q.Add("sendEmail", strconv.FormatBool(params.SendEmail)) - u.RawQuery = q.Encode() + uri.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return nil, fmt.Errorf("%w: failed to create request: %s", ErrResetUserPassword, err) } @@ -101,13 +90,19 @@ func (i *iam) ResetUserPassword(ctx context.Context, params ResetUserPasswordReq } func (i *iam) SetUserPassword(ctx context.Context, params SetUserPasswordRequest) error { + logger := i.Log(ctx) + logger.Debug("SetUserPassword") + if err := params.Validate(); err != nil { return fmt.Errorf("%s: %w:\n%s", ErrSetUserPassword, ErrStructValidation, err) } - u := fmt.Sprintf("/identity-management/v2/user-admin/ui-identities/%s/set-password", params.IdentityID) + uri, err := url.Parse(fmt.Sprintf("/identity-management/v3/user-admin/ui-identities/%s/set-password", params.IdentityID)) + if err != nil { + return fmt.Errorf("%w: failed to parse url: %s", ErrSetUserPassword, err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) if err != nil { return fmt.Errorf("%w: failed to create request: %s", ErrSetUserPassword, err) } diff --git a/pkg/iam/user_password_test.go b/pkg/iam/user_password_test.go index 37975419..3492b878 100644 --- a/pkg/iam/user_password_test.go +++ b/pkg/iam/user_password_test.go @@ -3,7 +3,7 @@ package iam import ( "context" "errors" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -33,7 +33,7 @@ func TestIAM_ResetUserPassword(t *testing.T) { expectedResponse: ResetUserPasswordResponse{ NewPassword: "K8QVa7Q2", }, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/reset-password?sendEmail=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/reset-password?sendEmail=false", }, "204 No Content": { params: ResetUserPasswordRequest{ @@ -42,7 +42,7 @@ func TestIAM_ResetUserPassword(t *testing.T) { }, responseStatus: http.StatusNoContent, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/reset-password?sendEmail=true", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/reset-password?sendEmail=true", }, "404 Not Found": { params: ResetUserPasswordRequest{ @@ -57,7 +57,7 @@ func TestIAM_ResetUserPassword(t *testing.T) { "title": "User not found", "type": "/useradmin-api/error-types/1100" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/X1-ABCDE/reset-password?sendEmail=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities/X1-ABCDE/reset-password?sendEmail=false", withError: func(t *testing.T, err error) { want := &Error{ Instance: "", @@ -82,7 +82,7 @@ func TestIAM_ResetUserPassword(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/reset-password?sendEmail=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/reset-password?sendEmail=false", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -95,23 +95,23 @@ func TestIAM_ResetUserPassword(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - response, err := client.ResetUserPassword(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + response, err := client.ResetUserPassword(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, *response) + assert.Equal(t, tc.expectedResponse, *response) }) } } @@ -133,7 +133,7 @@ func TestIAM_SetUserPassword(t *testing.T) { responseStatus: http.StatusNoContent, responseBody: "", expectedRequestBody: `{"newPassword":"newpwd"}`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/set-password", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/set-password", }, "400 Bad Request - same password": { params: SetUserPasswordRequest{ @@ -148,7 +148,7 @@ func TestIAM_SetUserPassword(t *testing.T) { "title": "Validation Exception", "type": "/useradmin-api/error-types/1508" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/X1-ABCDE/set-password", + expectedPath: "/identity-management/v3/user-admin/ui-identities/X1-ABCDE/set-password", withError: func(t *testing.T, err error) { want := &Error{ Instance: "", @@ -175,7 +175,7 @@ func TestIAM_SetUserPassword(t *testing.T) { "title": "User not found", "type": "/useradmin-api/error-types/1100" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/X1-ABCDE/set-password", + expectedPath: "/identity-management/v3/user-admin/ui-identities/X1-ABCDE/set-password", withError: func(t *testing.T, err error) { want := &Error{ Instance: "", @@ -201,7 +201,7 @@ func TestIAM_SetUserPassword(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/set-password", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/set-password", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -214,25 +214,25 @@ func TestIAM_SetUserPassword(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) - if len(test.expectedRequestBody) > 0 { - body, err := ioutil.ReadAll(r.Body) + if len(tc.expectedRequestBody) > 0 { + body, err := io.ReadAll(r.Body) require.NoError(t, err) - assert.Equal(t, test.expectedRequestBody, string(body)) + assert.Equal(t, tc.expectedRequestBody, string(body)) } })) client := mockAPIClient(t, mockServer) - err := client.SetUserPassword(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + err := client.SetUserPassword(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) diff --git a/pkg/iam/user_test.go b/pkg/iam/user_test.go index ff5ce976..f1138050 100644 --- a/pkg/iam/user_test.go +++ b/pkg/iam/user_test.go @@ -8,7 +8,8 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/internal/test" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -26,17 +27,17 @@ func TestIAM_CreateUser(t *testing.T) { "201 OK": { params: CreateUserRequest{ UserBasicInfo: UserBasicInfo{ - FirstName: "John", - LastName: "Doe", - Email: "john.doe@mycompany.com", - Phone: "(123) 321-1234", - Country: "USA", - State: "CA", + FirstName: "John", + LastName: "Doe", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + Country: "USA", + State: "CA", + AdditionalAuthentication: NoneAuthentication, }, - AuthGrants: []AuthGrantRequest{{GroupID: 1, RoleID: tools.IntPtr(1)}}, - Notifications: UserNotifications{}, + AuthGrants: []AuthGrantRequest{{GroupID: 1, RoleID: ptr.To(1)}}, }, - requestBody: `{"firstName":"John","lastName":"Doe","email":"john.doe@mycompany.com","phone":"(123) 321-1234","jobTitle":"","tfaEnabled":false,"state":"CA","country":"USA","authGrants":[{"groupId":1,"isBlocked":false,"roleId":1}],"notifications":{"enableEmailNotifications":false,"options":{"newUserNotification":false,"passwordExpiry":false,"proactive":null,"upgrade":null}}}`, + requestBody: `{"firstName":"John","lastName":"Doe","email":"john.doe@mycompany.com","phone":"(123) 321-1234","jobTitle":"","tfaEnabled":false,"state":"CA","country":"USA","additionalAuthentication":"NONE","authGrants":[{"groupId":1,"isBlocked":false,"roleId":1}]}`, responseStatus: http.StatusCreated, responseBody: ` { @@ -46,33 +47,107 @@ func TestIAM_CreateUser(t *testing.T) { "email": "john.doe@mycompany.com", "phone": "(123) 321-1234", "state": "CA", - "country": "USA" + "country": "USA", + "additionalAuthenticationConfigured": false, + "additionalAuthentication": "NONE" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities?sendEmail=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities?sendEmail=false", expectedResponse: &User{ IdentityID: "A-BC-1234567", UserBasicInfo: UserBasicInfo{ - FirstName: "John", - LastName: "Doe", - Email: "john.doe@mycompany.com", - Phone: "(123) 321-1234", - Country: "USA", - State: "CA", + FirstName: "John", + LastName: "Doe", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + Country: "USA", + State: "CA", + AdditionalAuthentication: NoneAuthentication, }, + AdditionalAuthenticationConfigured: false, + }, + }, + "201 OK - all fields": { + params: CreateUserRequest{ + UserBasicInfo: UserBasicInfo{ + FirstName: "John", + LastName: "Doe", + UserName: "UserName", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + TimeZone: "GMT+2", + JobTitle: "Title", + SecondaryEmail: "second@email.com", + MobilePhone: "123123123", + Address: "Address", + City: "City", + State: "CA", + ZipCode: "11-111", + Country: "USA", + ContactType: "Dev", + PreferredLanguage: "EN", + SessionTimeOut: ptr.To(1), + AdditionalAuthentication: MFAAuthentication, + }, + AuthGrants: []AuthGrantRequest{{GroupID: 1, RoleID: ptr.To(1)}}, + Notifications: &UserNotifications{ + EnableEmail: false, + Options: UserNotificationOptions{ + NewUser: false, + PasswordExpiry: false, + Proactive: []string{"Test1"}, + Upgrade: []string{"Test2"}, + APIClientCredentialExpiry: false, + }, + }, + SendEmail: true, + }, + requestBody: `{"firstName":"John","lastName":"Doe","uiUserName":"UserName","email":"john.doe@mycompany.com","phone":"(123) 321-1234","timeZone":"GMT+2","jobTitle":"Title","tfaEnabled":false,"secondaryEmail":"second@email.com","mobilePhone":"123123123","address":"Address","city":"City","state":"CA","zipCode":"11-111","country":"USA","contactType":"Dev","preferredLanguage":"EN","sessionTimeOut":1,"additionalAuthentication":"MFA","authGrants":[{"groupId":1,"isBlocked":false,"roleId":1}],"notifications":{"enableEmailNotifications":false,"options":{"newUserNotification":false,"passwordExpiry":false,"proactive":["Test1"],"upgrade":["Test2"],"apiClientCredentialExpiryNotification":false}}}`, + responseStatus: http.StatusCreated, + responseBody: ` +{ + "uiIdentityId": "A-BC-1234567", + "firstName": "John", + "lastName": "Doe", + "email": "john.doe@mycompany.com", + "phone": "(123) 321-1234", + "state": "CA", + "country": "USA", + "additionalAuthenticationConfigured": false, + "additionalAuthentication": "NONE" +}`, + expectedPath: "/identity-management/v3/user-admin/ui-identities?sendEmail=true", + expectedResponse: &User{ + IdentityID: "A-BC-1234567", + UserBasicInfo: UserBasicInfo{ + FirstName: "John", + LastName: "Doe", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + Country: "USA", + State: "CA", + AdditionalAuthentication: NoneAuthentication, + }, + AdditionalAuthenticationConfigured: false, + }, + }, + "validation errors": { + params: CreateUserRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "create user: struct validation:\nAdditionalAuthentication: cannot be blank\nAuthGrants: cannot be blank\nCountry: cannot be blank\nEmail: cannot be blank\nFirstName: cannot be blank\nLastName: cannot be blank", err.Error()) }, }, "500 internal server error": { params: CreateUserRequest{ UserBasicInfo: UserBasicInfo{ - FirstName: "John", - LastName: "Doe", - Email: "john.doe@mycompany.com", - Phone: "(123) 321-1234", - Country: "USA", - State: "CA", + FirstName: "John", + LastName: "Doe", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + Country: "USA", + State: "CA", + AdditionalAuthentication: TFAAuthentication, }, - AuthGrants: []AuthGrantRequest{{GroupID: 1, RoleID: tools.IntPtr(1)}}, - Notifications: UserNotifications{}, + AuthGrants: []AuthGrantRequest{{GroupID: 1, RoleID: ptr.To(1)}}, }, responseStatus: http.StatusInternalServerError, responseBody: ` @@ -82,7 +157,7 @@ func TestIAM_CreateUser(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities?sendEmail=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities?sendEmail=false", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -95,30 +170,30 @@ func TestIAM_CreateUser(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - w.WriteHeader(test.responseStatus) - if test.requestBody != "" { + w.WriteHeader(tc.responseStatus) + if tc.requestBody != "" { buf := new(bytes.Buffer) _, err := buf.ReadFrom(r.Body) assert.NoError(t, err) req := buf.String() - assert.Equal(t, test.requestBody, req) + assert.Equal(t, tc.requestBody, req) } - _, err := w.Write([]byte(test.responseBody)) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.CreateUser(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.CreateUser(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -145,9 +220,11 @@ func TestIAM_GetUser(t *testing.T) { "email": "john.doe@mycompany.com", "phone": "(123) 321-1234", "state": "CA", - "country": "USA" + "country": "USA", + "accountId": "sampleID", + "userStatus": "PENDING" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/A-BC-1234567?actions=false&authGrants=false¬ifications=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities/A-BC-1234567?actions=false&authGrants=false¬ifications=false", expectedResponse: &User{ IdentityID: "A-BC-1234567", UserBasicInfo: UserBasicInfo{ @@ -158,6 +235,8 @@ func TestIAM_GetUser(t *testing.T) { Country: "USA", State: "CA", }, + UserStatus: "PENDING", + AccountID: "sampleID", }, }, "500 internal server error": { @@ -172,7 +251,7 @@ func TestIAM_GetUser(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/A-BC-1234567?actions=false&authGrants=false¬ifications=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities/A-BC-1234567?actions=false&authGrants=false¬ifications=false", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -185,28 +264,28 @@ func TestIAM_GetUser(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.GetUser(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.GetUser(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } -func TestIam_ListUsers(t *testing.T) { +func TestIAM_ListUsers(t *testing.T) { tests := map[string]struct { params ListUsersRequest responseStatus int @@ -217,12 +296,12 @@ func TestIam_ListUsers(t *testing.T) { }{ "200 OK": { params: ListUsersRequest{ - GroupID: tools.Int64Ptr(12345), + GroupID: ptr.To(int64(12345)), AuthGrants: true, Actions: true, }, responseStatus: http.StatusOK, - expectedPath: "/identity-management/v2/user-admin/ui-identities?actions=true&authGrants=true&groupId=12345", + expectedPath: "/identity-management/v3/user-admin/ui-identities?actions=true&authGrants=true&groupId=12345", responseBody: `[ { "uiIdentityId": "A-B-123456", @@ -231,10 +310,12 @@ func TestIam_ListUsers(t *testing.T) { "uiUserName": "johndoe", "email": "john.doe@mycompany.com", "accountId": "1-123A", - "lastLoginDate": "2016-01-13T17:53:57Z", + "lastLoginDate": "2016-01-13T17:53:57.000Z", "tfaEnabled": true, "tfaConfigured": true, "isLocked": false, + "additionalAuthentication": "TFA", + "additionalAuthenticationConfigured": false, "actions": { "resetPassword": true, "delete": true, @@ -243,7 +324,9 @@ func TestIam_ListUsers(t *testing.T) { "thirdPartyAccess": true, "isCloneable": true, "editProfile": true, - "canEditTFA": false + "canEditTFA": true, + "canEditMFA": true, + "canEditNone": true }, "authGrants": [ { @@ -259,16 +342,18 @@ func TestIam_ListUsers(t *testing.T) { ]`, expectedResponse: []UserListItem{ { - IdentityID: "A-B-123456", - FirstName: "John", - LastName: "Doe", - UserName: "johndoe", - Email: "john.doe@mycompany.com", - AccountID: "1-123A", - TFAEnabled: true, - LastLoginDate: "2016-01-13T17:53:57Z", - TFAConfigured: true, - IsLocked: false, + IdentityID: "A-B-123456", + FirstName: "John", + LastName: "Doe", + UserName: "johndoe", + Email: "john.doe@mycompany.com", + AccountID: "1-123A", + TFAEnabled: true, + LastLoginDate: test.NewTimeFromString(t, "2016-01-13T17:53:57.000Z"), + TFAConfigured: true, + IsLocked: false, + AdditionalAuthentication: TFAAuthentication, + AdditionalAuthenticationConfigured: false, Actions: &UserActions{ APIClient: true, Delete: true, @@ -277,11 +362,14 @@ func TestIam_ListUsers(t *testing.T) { ResetPassword: true, ThirdPartyAccess: true, EditProfile: true, + CanEditMFA: true, + CanEditNone: true, + CanEditTFA: true, }, AuthGrants: []AuthGrant{ { GroupID: 12345, - RoleID: tools.IntPtr(12), + RoleID: ptr.To(12), GroupName: "mygroup", RoleName: "admin", RoleDescription: "This is a new role that has been created to", @@ -292,10 +380,10 @@ func TestIam_ListUsers(t *testing.T) { }, "200 OK, no actions nor grants": { params: ListUsersRequest{ - GroupID: tools.Int64Ptr(12345), + GroupID: ptr.To(int64(12345)), }, responseStatus: http.StatusOK, - expectedPath: "/identity-management/v2/user-admin/ui-identities?actions=false&authGrants=false&groupId=12345", + expectedPath: "/identity-management/v3/user-admin/ui-identities?actions=false&authGrants=false&groupId=12345", responseBody: `[ { "uiIdentityId": "A-B-123456", @@ -304,30 +392,34 @@ func TestIam_ListUsers(t *testing.T) { "uiUserName": "johndoe", "email": "john.doe@mycompany.com", "accountId": "1-123A", - "lastLoginDate": "2016-01-13T17:53:57Z", + "lastLoginDate": "2016-01-13T17:53:57.000Z", "tfaEnabled": true, "tfaConfigured": true, - "isLocked": false + "isLocked": false, + "additionalAuthentication": "MFA", + "additionalAuthenticationConfigured": true } ]`, expectedResponse: []UserListItem{ { - IdentityID: "A-B-123456", - FirstName: "John", - LastName: "Doe", - UserName: "johndoe", - Email: "john.doe@mycompany.com", - AccountID: "1-123A", - TFAEnabled: true, - LastLoginDate: "2016-01-13T17:53:57Z", - TFAConfigured: true, - IsLocked: false, + IdentityID: "A-B-123456", + FirstName: "John", + LastName: "Doe", + UserName: "johndoe", + Email: "john.doe@mycompany.com", + AccountID: "1-123A", + TFAEnabled: true, + LastLoginDate: test.NewTimeFromString(t, "2016-01-13T17:53:57.000Z"), + TFAConfigured: true, + IsLocked: false, + AdditionalAuthenticationConfigured: true, + AdditionalAuthentication: MFAAuthentication, }, }, }, - "no group id": { + "200 OK, no group id": { params: ListUsersRequest{}, - expectedPath: "/identity-management/v2/user-admin/ui-identities?actions=false&authGrants=false", + expectedPath: "/identity-management/v3/user-admin/ui-identities?actions=false&authGrants=false", responseBody: `[ { "uiIdentityId": "A-B-123456", @@ -336,36 +428,40 @@ func TestIam_ListUsers(t *testing.T) { "uiUserName": "johndoe", "email": "john.doe@mycompany.com", "accountId": "1-123A", - "lastLoginDate": "2016-01-13T17:53:57Z", + "lastLoginDate": "2016-01-13T17:53:57.000Z", "tfaEnabled": true, "tfaConfigured": true, - "isLocked": false + "isLocked": false, + "additionalAuthentication": "TFA", + "additionalAuthenticationConfigured": true } ]`, expectedResponse: []UserListItem{ { - IdentityID: "A-B-123456", - FirstName: "John", - LastName: "Doe", - UserName: "johndoe", - Email: "john.doe@mycompany.com", - AccountID: "1-123A", - TFAEnabled: true, - LastLoginDate: "2016-01-13T17:53:57Z", - TFAConfigured: true, - IsLocked: false, + IdentityID: "A-B-123456", + FirstName: "John", + LastName: "Doe", + UserName: "johndoe", + Email: "john.doe@mycompany.com", + AccountID: "1-123A", + TFAEnabled: true, + LastLoginDate: test.NewTimeFromString(t, "2016-01-13T17:53:57.000Z"), + TFAConfigured: true, + IsLocked: false, + AdditionalAuthentication: TFAAuthentication, + AdditionalAuthenticationConfigured: true, }, }, responseStatus: http.StatusOK, }, "500 internal server error": { params: ListUsersRequest{ - GroupID: tools.Int64Ptr(12345), + GroupID: ptr.To(int64(12345)), AuthGrants: true, Actions: true, }, responseStatus: http.StatusInternalServerError, - expectedPath: "/identity-management/v2/user-admin/ui-identities?actions=true&authGrants=true&groupId=12345", + expectedPath: "/identity-management/v3/user-admin/ui-identities?actions=true&authGrants=true&groupId=12345", responseBody: ` { "type": "internal_error", @@ -384,23 +480,23 @@ func TestIam_ListUsers(t *testing.T) { }, }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - users, err := client.ListUsers(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + users, err := client.ListUsers(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } assert.NoError(t, err) - assert.Equal(t, test.expectedResponse, users) + assert.Equal(t, tc.expectedResponse, users) }) } } @@ -419,19 +515,20 @@ func TestIAM_UpdateUserInfo(t *testing.T) { params: UpdateUserInfoRequest{ IdentityID: "1-ABCDE", User: UserBasicInfo{ - FirstName: "John", - LastName: "Doe", - Email: "john.doe@mycompany.com", - Phone: "(123) 321-1234", - Country: "USA", - State: "CA", - PreferredLanguage: "English", - ContactType: "Billing", - SessionTimeOut: tools.IntPtr(30), - TimeZone: "GMT", + FirstName: "John", + LastName: "Doe", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + Country: "USA", + State: "CA", + PreferredLanguage: "English", + ContactType: "Billing", + SessionTimeOut: ptr.To(30), + TimeZone: "GMT", + AdditionalAuthentication: NoneAuthentication, }, }, - requestBody: `{"firstName":"John","lastName":"Doe","email":"john.doe@mycompany.com","phone":"(123) 321-1234","timeZone":"GMT","jobTitle":"","tfaEnabled":false,"state":"CA","country":"USA","contactType":"Billing","preferredLanguage":"English","sessionTimeOut":30}`, + requestBody: `{"firstName":"John","lastName":"Doe","email":"john.doe@mycompany.com","phone":"(123) 321-1234","timeZone":"GMT","jobTitle":"","tfaEnabled":false,"state":"CA","country":"USA","contactType":"Billing","preferredLanguage":"English","sessionTimeOut":30,"additionalAuthentication":"NONE"}`, responseStatus: http.StatusOK, responseBody: ` { @@ -444,20 +541,22 @@ func TestIAM_UpdateUserInfo(t *testing.T) { "preferredLanguage": "English", "contactType": "Billing", "sessionTimeOut": 30, - "timeZone": "GMT" + "timeZone": "GMT", + "additionalAuthentication": "NONE" }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/basic-info", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/basic-info", expectedResponse: &UserBasicInfo{ - FirstName: "John", - LastName: "Doe", - Email: "john.doe@mycompany.com", - Phone: "(123) 321-1234", - Country: "USA", - State: "CA", - PreferredLanguage: "English", - ContactType: "Billing", - SessionTimeOut: tools.IntPtr(30), - TimeZone: "GMT", + FirstName: "John", + LastName: "Doe", + Email: "john.doe@mycompany.com", + Phone: "(123) 321-1234", + Country: "USA", + State: "CA", + PreferredLanguage: "English", + ContactType: "Billing", + SessionTimeOut: ptr.To(30), + TimeZone: "GMT", + AdditionalAuthentication: NoneAuthentication, }, }, "500 internal server error": { @@ -472,7 +571,7 @@ func TestIAM_UpdateUserInfo(t *testing.T) { State: "CA", PreferredLanguage: "English", ContactType: "Billing", - SessionTimeOut: tools.IntPtr(30), + SessionTimeOut: ptr.To(30), TimeZone: "GMT", }, }, @@ -484,7 +583,7 @@ func TestIAM_UpdateUserInfo(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/basic-info", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/basic-info", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -497,30 +596,30 @@ func TestIAM_UpdateUserInfo(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - if test.requestBody != "" { + if tc.requestBody != "" { buf := new(bytes.Buffer) _, err := buf.ReadFrom(r.Body) assert.NoError(t, err) req := buf.String() - assert.Equal(t, test.requestBody, req) + assert.Equal(t, tc.requestBody, req) } - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.UpdateUserInfo(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.UpdateUserInfo(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -538,17 +637,18 @@ func TestIAM_UpdateUserNotifications(t *testing.T) { "200 OK": { params: UpdateUserNotificationsRequest{ IdentityID: "1-ABCDE", - Notifications: UserNotifications{ + Notifications: &UserNotifications{ EnableEmail: true, Options: UserNotificationOptions{ - Upgrade: []string{"NetStorage", "Other Upgrade Notifications (Planned)"}, - Proactive: []string{"EdgeScape", "EdgeSuite (HTTP Content Delivery)"}, - PasswordExpiry: true, - NewUser: true, + Upgrade: []string{"NetStorage", "Other Upgrade Notifications (Planned)"}, + Proactive: []string{"EdgeScape", "EdgeSuite (HTTP Content Delivery)"}, + PasswordExpiry: true, + NewUser: true, + APIClientCredentialExpiry: true, }, }, }, - requestBody: `{"enableEmailNotifications":true,"options":{"newUserNotification":true,"passwordExpiry":true,"proactive":["EdgeScape","EdgeSuite (HTTP Content Delivery)"],"upgrade":["NetStorage","Other Upgrade Notifications (Planned)"]}}`, + requestBody: `{"enableEmailNotifications":true,"options":{"newUserNotification":true,"passwordExpiry":true,"proactive":["EdgeScape","EdgeSuite (HTTP Content Delivery)"],"upgrade":["NetStorage","Other Upgrade Notifications (Planned)"],"apiClientCredentialExpiryNotification":true}}`, responseStatus: http.StatusOK, responseBody: ` { @@ -563,24 +663,32 @@ func TestIAM_UpdateUserNotifications(t *testing.T) { "EdgeSuite (HTTP Content Delivery)" ], "passwordExpiry": true, - "newUserNotification": true + "newUserNotification": true, + "apiClientCredentialExpiryNotification": true } }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/notifications", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/notifications", expectedResponse: &UserNotifications{ EnableEmail: true, Options: UserNotificationOptions{ - Upgrade: []string{"NetStorage", "Other Upgrade Notifications (Planned)"}, - Proactive: []string{"EdgeScape", "EdgeSuite (HTTP Content Delivery)"}, - PasswordExpiry: true, - NewUser: true, + Upgrade: []string{"NetStorage", "Other Upgrade Notifications (Planned)"}, + Proactive: []string{"EdgeScape", "EdgeSuite (HTTP Content Delivery)"}, + PasswordExpiry: true, + NewUser: true, + APIClientCredentialExpiry: true, }, }, }, + "validation errors": { + params: UpdateUserNotificationsRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "update user notifications: struct validation:\nIdentityID: cannot be blank\nNotifications: cannot be blank", err.Error()) + }, + }, "500 internal server error": { params: UpdateUserNotificationsRequest{ IdentityID: "1-ABCDE", - Notifications: UserNotifications{ + Notifications: &UserNotifications{ EnableEmail: true, Options: UserNotificationOptions{ Upgrade: []string{"NetStorage", "Other Upgrade Notifications (Planned)"}, @@ -598,7 +706,7 @@ func TestIAM_UpdateUserNotifications(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/notifications", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/notifications", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -611,30 +719,30 @@ func TestIAM_UpdateUserNotifications(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - if test.requestBody != "" { + if tc.requestBody != "" { buf := new(bytes.Buffer) _, err := buf.ReadFrom(r.Body) assert.NoError(t, err) req := buf.String() - assert.Equal(t, test.requestBody, req) + assert.Equal(t, tc.requestBody, req) } - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.UpdateUserNotifications(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.UpdateUserNotifications(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -654,7 +762,7 @@ func TestIAM_UpdateUserAuthGrants(t *testing.T) { AuthGrants: []AuthGrantRequest{ { GroupID: 12345, - RoleID: tools.IntPtr(16), + RoleID: ptr.To(16), Subgroups: []AuthGrantRequest{ { GroupID: 54321, @@ -676,11 +784,11 @@ func TestIAM_UpdateUserAuthGrants(t *testing.T) { ] } ]`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/auth-grants", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/auth-grants", expectedResponse: []AuthGrant{ { GroupID: 12345, - RoleID: tools.IntPtr(16), + RoleID: ptr.To(16), Subgroups: []AuthGrant{ { GroupID: 54321, @@ -695,7 +803,7 @@ func TestIAM_UpdateUserAuthGrants(t *testing.T) { AuthGrants: []AuthGrantRequest{ { GroupID: 12345, - RoleID: tools.IntPtr(16), + RoleID: ptr.To(16), Subgroups: []AuthGrantRequest{ { GroupID: 54321, @@ -712,7 +820,7 @@ func TestIAM_UpdateUserAuthGrants(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE/auth-grants", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/auth-grants", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -725,23 +833,23 @@ func TestIAM_UpdateUserAuthGrants(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - result, err := client.UpdateUserAuthGrants(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + result, err := client.UpdateUserAuthGrants(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) - assert.Equal(t, test.expectedResponse, result) + assert.Equal(t, tc.expectedResponse, result) }) } } @@ -760,7 +868,7 @@ func TestIAM_RemoveUser(t *testing.T) { }, responseStatus: http.StatusOK, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE", }, "204 No Content": { params: RemoveUserRequest{ @@ -768,7 +876,7 @@ func TestIAM_RemoveUser(t *testing.T) { }, responseStatus: http.StatusNoContent, responseBody: "", - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE", }, "500 internal server error": { params: RemoveUserRequest{ @@ -782,7 +890,7 @@ func TestIAM_RemoveUser(t *testing.T) { "detail": "Error making request", "status": 500 }`, - expectedPath: "/identity-management/v2/user-admin/ui-identities/1-ABCDE", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE", withError: func(t *testing.T, err error) { want := &Error{ Type: "internal_error", @@ -795,19 +903,19 @@ func TestIAM_RemoveUser(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodDelete, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.RemoveUser(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + err := client.RemoveUser(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) @@ -858,19 +966,143 @@ func TestIAM_UpdateTFA(t *testing.T) { }, } - for name, test := range tests { + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + err := client.UpdateTFA(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_UpdateMFA(t *testing.T) { + tests := map[string]struct { + params UpdateMFARequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 No Content": { + params: UpdateMFARequest{ + IdentityID: "1-ABCDE", + Value: MFAAuthentication, + }, + responseStatus: http.StatusNoContent, + responseBody: "", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/additionalAuthentication", + }, + "500 internal server error": { + params: UpdateMFARequest{ + IdentityID: "1-ABCDE", + Value: MFAAuthentication, + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +}`, + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/additionalAuthentication", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + err := client.UpdateMFA(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestIAM_ResetMFA(t *testing.T) { + tests := map[string]struct { + params ResetMFARequest + responseStatus int + responseBody string + expectedPath string + withError func(*testing.T, error) + }{ + "204 No Content": { + params: ResetMFARequest{ + IdentityID: "1-ABCDE", + }, + responseStatus: http.StatusNoContent, + responseBody: "", + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/additionalAuthentication/reset", + }, + "500 internal server error": { + params: ResetMFARequest{ + IdentityID: "1-ABCDE", + }, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error making request", + "status": 500 +}`, + expectedPath: "/identity-management/v3/user-admin/ui-identities/1-ABCDE/additionalAuthentication/reset", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error making request", + StatusCode: http.StatusInternalServerError, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + } + + for name, tc := range tests { t.Run(name, func(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, tc.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPut, r.Method) - w.WriteHeader(test.responseStatus) - _, err := w.Write([]byte(test.responseBody)) + w.WriteHeader(tc.responseStatus) + _, err := w.Write([]byte(tc.responseBody)) assert.NoError(t, err) })) client := mockAPIClient(t, mockServer) - err := client.UpdateTFA(context.Background(), test.params) - if test.withError != nil { - test.withError(t, err) + err := client.ResetMFA(context.Background(), tc.params) + if tc.withError != nil { + tc.withError(t, err) return } require.NoError(t, err) diff --git a/pkg/imaging/errors.go b/pkg/imaging/errors.go index 9eb00c6d..c2909628 100644 --- a/pkg/imaging/errors.go +++ b/pkg/imaging/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/imaging/errors_test.go b/pkg/imaging/errors_test.go index 19001aa2..22464015 100644 --- a/pkg/imaging/errors_test.go +++ b/pkg/imaging/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/imaging/imaging.go b/pkg/imaging/imaging.go index f8e701b7..e3ec9c87 100644 --- a/pkg/imaging/imaging.go +++ b/pkg/imaging/imaging.go @@ -2,9 +2,10 @@ package imaging import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,8 +16,43 @@ var ( type ( // Imaging is the api interface for Image and Video Manager Imaging interface { - Policies - PolicySets + // Policies + + // ListPolicies lists all Policies for the given network and an account + // See: https://techdocs.akamai.com/ivm/reference/get-policies + ListPolicies(context.Context, ListPoliciesRequest) (*ListPoliciesResponse, error) + + // GetPolicy gets specific policy by PolicyID + GetPolicy(context.Context, GetPolicyRequest) (PolicyOutput, error) + + // UpsertPolicy creates or updates the configuration for a policy + UpsertPolicy(context.Context, UpsertPolicyRequest) (*PolicyResponse, error) + + // DeletePolicy deletes a policy + DeletePolicy(context.Context, DeletePolicyRequest) (*PolicyResponse, error) + + // GetPolicyHistory retrieves history of changes for a policy + GetPolicyHistory(context.Context, GetPolicyHistoryRequest) (*GetPolicyHistoryResponse, error) + + // RollbackPolicy reverts a policy to its previous version and deploys it to the network + RollbackPolicy(ctx context.Context, request RollbackPolicyRequest) (*PolicyResponse, error) + + // PolicySets + + // ListPolicySets lists all PolicySets of specified type for the current account + ListPolicySets(context.Context, ListPolicySetsRequest) ([]PolicySet, error) + + // GetPolicySet gets specific PolicySet by PolicySetID + GetPolicySet(context.Context, GetPolicySetRequest) (*PolicySet, error) + + // CreatePolicySet creates configuration for an PolicySet + CreatePolicySet(context.Context, CreatePolicySetRequest) (*PolicySet, error) + + // UpdatePolicySet creates configuration for an PolicySet + UpdatePolicySet(context.Context, UpdatePolicySetRequest) (*PolicySet, error) + + // DeletePolicySet deletes configuration for an PolicySet + DeletePolicySet(context.Context, DeletePolicySetRequest) error } imaging struct { diff --git a/pkg/imaging/imaging_test.go b/pkg/imaging/imaging_test.go index 94138655..4ac04d2d 100644 --- a/pkg/imaging/imaging_test.go +++ b/pkg/imaging/imaging_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/imaging/policy.go b/pkg/imaging/policy.go index 58365590..8407fa93 100644 --- a/pkg/imaging/policy.go +++ b/pkg/imaging/policy.go @@ -7,7 +7,7 @@ import ( "fmt" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) @@ -17,31 +17,6 @@ import ( // https://git.source.akamai.com/users/eleclair/repos/terraform/browse/docs/schemas type ( - // Policies is an Image and Video Manager API interface for Policy - // - // See: https://techdocs.akamai.com/ivm/reference/api - Policies interface { - // ListPolicies lists all Policies for the given network and an account - // - // See: https://techdocs.akamai.com/ivm/reference/get-policies - ListPolicies(context.Context, ListPoliciesRequest) (*ListPoliciesResponse, error) - - // GetPolicy gets specific policy by PolicyID - GetPolicy(context.Context, GetPolicyRequest) (PolicyOutput, error) - - // UpsertPolicy creates or updates the configuration for a policy - UpsertPolicy(context.Context, UpsertPolicyRequest) (*PolicyResponse, error) - - // DeletePolicy deletes a policy - DeletePolicy(context.Context, DeletePolicyRequest) (*PolicyResponse, error) - - // GetPolicyHistory retrieves history of changes for a policy - GetPolicyHistory(context.Context, GetPolicyHistoryRequest) (*GetPolicyHistoryResponse, error) - - // RollbackPolicy reverts a policy to its previous version and deploys it to the network - RollbackPolicy(ctx context.Context, request RollbackPolicyRequest) (*PolicyResponse, error) - } - // ListPoliciesRequest describes the parameters of the ListPolicies request ListPoliciesRequest struct { Network PolicyNetwork diff --git a/pkg/imaging/policy_test.go b/pkg/imaging/policy_test.go index 5be95c58..1ed2e783 100644 --- a/pkg/imaging/policy_test.go +++ b/pkg/imaging/policy_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -269,7 +269,7 @@ func TestListPolicies(t *testing.T) { EndTime: 1626379177, RolloutDuration: 1, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "system", DateCreated: "2021-07-15 19:59:35+0000", }, @@ -294,10 +294,10 @@ func TestListPolicies(t *testing.T) { &Composite{ Transformation: "Composite", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr(GravityNorthWest), @@ -308,19 +308,19 @@ func TestListPolicies(t *testing.T) { Image: &TextImageType{ Type: "Text", Fill: &StringVariableInline{ - Value: tools.StringPtr("#000000"), + Value: ptr.To("#000000"), }, Size: &NumberVariableInline{ - Value: tools.Float64Ptr(72), + Value: ptr.To(float64(72)), }, Stroke: &StringVariableInline{ - Value: tools.StringPtr("#FFFFFF"), + Value: ptr.To("#FFFFFF"), }, StrokeSize: &NumberVariableInline{ - Value: tools.Float64Ptr(0), + Value: ptr.To(float64(0)), }, Text: &StringVariableInline{ - Value: tools.StringPtr("Hello There"), + Value: ptr.To("Hello There"), }, Transformation: &Compound{ Transformation: "Compound", @@ -328,7 +328,7 @@ func TestListPolicies(t *testing.T) { }, }, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "jsmith", DateCreated: "2021-12-07 16:20:34+0000", }, @@ -349,7 +349,7 @@ func TestListPolicies(t *testing.T) { Value: OutputImagePerceptualQualityPtr(OutputImagePerceptualQualityMediumHigh), }, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "jsmith", DateCreated: "2021-08-06 19:46:32+0000", }, @@ -374,13 +374,13 @@ func TestListPolicies(t *testing.T) { &Blur{ Transformation: "Blur", Sigma: &NumberVariableInline{ - Name: tools.StringPtr("blurVar"), + Name: ptr.To("blurVar"), }, }, &MaxColors{ Transformation: "MaxColors", Colors: &IntegerVariableInline{ - Value: tools.IntPtr(2), + Value: ptr.To(2), }, }, }, @@ -391,7 +391,7 @@ func TestListPolicies(t *testing.T) { DefaultValue: "5", }, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "foofoo5", DateCreated: "2021-12-16 18:46:38+0000", }, @@ -416,13 +416,13 @@ func TestListPolicies(t *testing.T) { &Blur{ Transformation: "Blur", Sigma: &NumberVariableInline{ - Name: tools.StringPtr("blurVar"), + Name: ptr.To("blurVar"), }, }, &MaxColors{ Transformation: "MaxColors", Colors: &IntegerVariableInline{ - Value: tools.IntPtr(2), + Value: ptr.To(2), }, }, }, @@ -433,7 +433,7 @@ func TestListPolicies(t *testing.T) { DefaultValue: "5", }, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "foofoo5", DateCreated: "2021-12-16 18:47:36+0000", }, @@ -446,7 +446,7 @@ func TestListPolicies(t *testing.T) { EndTime: 1643052401, RolloutDuration: 1, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "jsmith", DateCreated: "2022-01-24 19:26:39+0000", }, @@ -459,7 +459,7 @@ func TestListPolicies(t *testing.T) { EndTime: 1643052165, RolloutDuration: 1, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "jsmith", DateCreated: "2022-01-24 19:22:43+0000", }, @@ -477,7 +477,7 @@ func TestListPolicies(t *testing.T) { Value: OutputVideoPerceptualQualityPtr(OutputVideoPerceptualQualityMediumHigh), }, }, - Video: tools.BoolPtr(true), + Video: ptr.To(true), User: "jsmith", DateCreated: "2022-01-24 20:17:10+0000", }, @@ -760,10 +760,10 @@ func TestListPolicies(t *testing.T) { &Trim{ Transformation: "Trim", Fuzz: &NumberVariableInline{ - Value: tools.Float64Ptr(0.08), + Value: ptr.To(0.08), }, Padding: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, }, &IfDimension{ @@ -772,7 +772,7 @@ func TestListPolicies(t *testing.T) { Value: IfDimensionDimensionPtr("width"), }, Value: &IntegerVariableInline{ - Name: tools.StringPtr("MaxDimOld"), + Name: ptr.To("MaxDimOld"), }, Default: &Compound{ Transformation: "Compound", @@ -783,7 +783,7 @@ func TestListPolicies(t *testing.T) { Value: IfDimensionDimensionPtr("width"), }, Value: &IntegerVariableInline{ - Name: tools.StringPtr("MinDim"), + Name: ptr.To("MinDim"), }, LessThan: &Compound{ Transformation: "Compound", @@ -797,37 +797,37 @@ func TestListPolicies(t *testing.T) { Value: ResizeTypePtr("normal"), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDimWithBorder"), + Name: ptr.To("ResizeDimWithBorder"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDimWithBorder"), + Name: ptr.To("ResizeDimWithBorder"), }, }, &Crop{ Transformation: "Crop", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr("Center"), }, AllowExpansion: &BooleanVariableInline{ - Value: tools.BoolPtr(true), + Value: ptr.To(true), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, }, &BackgroundColor{ Transformation: "BackgroundColor", Color: &StringVariableInline{ - Value: tools.StringPtr("#ffffff"), + Value: ptr.To("#ffffff"), }, }, }, @@ -841,7 +841,7 @@ func TestListPolicies(t *testing.T) { Value: IfDimensionDimensionPtr("height"), }, Value: &IntegerVariableInline{ - Name: tools.StringPtr("MinDim"), + Name: ptr.To("MinDim"), }, LessThan: &Compound{ Transformation: "Compound", @@ -855,37 +855,37 @@ func TestListPolicies(t *testing.T) { Value: ResizeTypePtr("normal"), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDimWithBorder"), + Name: ptr.To("ResizeDimWithBorder"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDimWithBorder"), + Name: ptr.To("ResizeDimWithBorder"), }, }, &Crop{ Transformation: "Crop", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr("Center"), }, AllowExpansion: &BooleanVariableInline{ - Value: tools.BoolPtr(true), + Value: ptr.To(true), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, }, &BackgroundColor{ Transformation: "BackgroundColor", Color: &StringVariableInline{ - Value: tools.StringPtr("#ffffff"), + Value: ptr.To("#ffffff"), }, }, }, @@ -899,7 +899,7 @@ func TestListPolicies(t *testing.T) { Value: IfDimensionDimensionPtr("height"), }, Value: &IntegerVariableInline{ - Name: tools.StringPtr("MaxDimOld"), + Name: ptr.To("MaxDimOld"), }, GreaterThan: &Compound{ Transformation: "Compound", @@ -914,37 +914,37 @@ func TestListPolicies(t *testing.T) { }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDimWithBorder"), + Name: ptr.To("ResizeDimWithBorder"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDimWithBorder"), + Name: ptr.To("ResizeDimWithBorder"), }, }, &Crop{ Transformation: "Crop", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr("Center"), }, AllowExpansion: &BooleanVariableInline{ - Value: tools.BoolPtr(true), + Value: ptr.To(true), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, }, &BackgroundColor{ Transformation: "BackgroundColor", Color: &StringVariableInline{ - Value: tools.StringPtr("#ffffff"), + Value: ptr.To("#ffffff"), }, }, }, @@ -961,37 +961,37 @@ func TestListPolicies(t *testing.T) { Value: ResizeTypePtr("normal"), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, }, &Crop{ Transformation: "Crop", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr("Center"), }, AllowExpansion: &BooleanVariableInline{ - Value: tools.BoolPtr(true), + Value: ptr.To(true), }, Width: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, Height: &IntegerVariableInline{ - Name: tools.StringPtr("ResizeDim"), + Name: ptr.To("ResizeDim"), }, }, &BackgroundColor{ Transformation: "BackgroundColor", Color: &StringVariableInline{ - Value: tools.StringPtr("#ffffff"), + Value: ptr.To("#ffffff"), }, }, }, @@ -1014,9 +1014,9 @@ func TestListPolicies(t *testing.T) { PerceptualQuality: &OutputImagePerceptualQualityVariableInline{ Value: OutputImagePerceptualQualityPtr("mediumHigh"), }, - AdaptiveQuality: tools.IntPtr(50), + AdaptiveQuality: ptr.To(50), }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), ID: "multidimension", DateCreated: "2022-01-01 12:00:00+0000", PreviousVersion: 0, @@ -1274,14 +1274,14 @@ func TestGetPolicy(t *testing.T) { Transformation: "Append", Gravity: &GravityVariableInline{Value: GravityPtr("Center")}, GravityPriority: &AppendGravityPriorityVariableInline{Value: AppendGravityPriorityPtr("horizontal")}, - PreserveMinorDimension: &BooleanVariableInline{Value: tools.BoolPtr(true)}, + PreserveMinorDimension: &BooleanVariableInline{Value: ptr.To(true)}, Image: &TextImageType{ Type: "Text", - Fill: &StringVariableInline{Value: tools.StringPtr("#000000")}, - Size: &NumberVariableInline{Value: tools.Float64Ptr(72)}, - Stroke: &StringVariableInline{Value: tools.StringPtr("#FFFFFF")}, - StrokeSize: &NumberVariableInline{Value: tools.Float64Ptr(0)}, - Text: &StringVariableInline{Value: tools.StringPtr("test")}, + Fill: &StringVariableInline{Value: ptr.To("#000000")}, + Size: &NumberVariableInline{Value: ptr.To(float64(72))}, + Stroke: &StringVariableInline{Value: ptr.To("#FFFFFF")}, + StrokeSize: &NumberVariableInline{Value: ptr.To(float64(0))}, + Text: &StringVariableInline{Value: ptr.To("test")}, Transformation: &Compound{ Transformation: "Compound", }, @@ -1291,24 +1291,24 @@ func TestGetPolicy(t *testing.T) { Transformation: "RegionOfInterestCrop", Style: &RegionOfInterestCropStyleVariableInline{Value: RegionOfInterestCropStylePtr("fill")}, Gravity: &GravityVariableInline{Value: GravityPtr("Center")}, - Width: &IntegerVariableInline{Value: tools.IntPtr(7)}, - Height: &IntegerVariableInline{Value: tools.IntPtr(8)}, + Width: &IntegerVariableInline{Value: ptr.To(7)}, + Height: &IntegerVariableInline{Value: ptr.To(8)}, RegionOfInterest: &RectangleShapeType{ Anchor: &PointShapeType{ - X: &NumberVariableInline{Value: tools.Float64Ptr(4)}, - Y: &NumberVariableInline{Value: tools.Float64Ptr(5)}, + X: &NumberVariableInline{Value: ptr.To(float64(4))}, + Y: &NumberVariableInline{Value: ptr.To(float64(5))}, }, - Width: &NumberVariableInline{Value: tools.Float64Ptr(8)}, - Height: &NumberVariableInline{Value: tools.Float64Ptr(9)}, + Width: &NumberVariableInline{Value: ptr.To(float64(8))}, + Height: &NumberVariableInline{Value: ptr.To(float64(9))}, }, }, &Composite{ Transformation: "Composite", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr(GravityNorthWest), @@ -1319,19 +1319,19 @@ func TestGetPolicy(t *testing.T) { Image: &TextImageType{ Type: "Text", Fill: &StringVariableInline{ - Value: tools.StringPtr("#000000"), + Value: ptr.To("#000000"), }, Size: &NumberVariableInline{ - Value: tools.Float64Ptr(72), + Value: ptr.To(float64(72)), }, Stroke: &StringVariableInline{ - Value: tools.StringPtr("#FFFFFF"), + Value: ptr.To("#FFFFFF"), }, StrokeSize: &NumberVariableInline{ - Value: tools.Float64Ptr(0), + Value: ptr.To(float64(0)), }, Text: &StringVariableInline{ - Value: tools.StringPtr("Hello There"), + Value: ptr.To("Hello There"), }, Transformation: &Compound{ Transformation: "Compound", @@ -1339,7 +1339,7 @@ func TestGetPolicy(t *testing.T) { }, }, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "jsmith", DateCreated: "2021-12-07 16:20:34+0000", }, @@ -1512,14 +1512,14 @@ func TestGetPolicy(t *testing.T) { Transformation: "Append", Gravity: &GravityVariableInline{Value: GravityPtr("Center")}, GravityPriority: &AppendGravityPriorityVariableInline{Value: AppendGravityPriorityPtr("horizontal")}, - PreserveMinorDimension: &BooleanVariableInline{Value: tools.BoolPtr(true)}, + PreserveMinorDimension: &BooleanVariableInline{Value: ptr.To(true)}, Image: &TextImageType{ Type: "Text", - Fill: &StringVariableInline{Value: tools.StringPtr("#000000")}, - Size: &NumberVariableInline{Value: tools.Float64Ptr(72)}, - Stroke: &StringVariableInline{Value: tools.StringPtr("#FFFFFF")}, - StrokeSize: &NumberVariableInline{Value: tools.Float64Ptr(0)}, - Text: &StringVariableInline{Value: tools.StringPtr("test")}, + Fill: &StringVariableInline{Value: ptr.To("#000000")}, + Size: &NumberVariableInline{Value: ptr.To(float64(72))}, + Stroke: &StringVariableInline{Value: ptr.To("#FFFFFF")}, + StrokeSize: &NumberVariableInline{Value: ptr.To(float64(0))}, + Text: &StringVariableInline{Value: ptr.To("test")}, Transformation: &Compound{ Transformation: "Compound", }, @@ -1529,24 +1529,24 @@ func TestGetPolicy(t *testing.T) { Transformation: "RegionOfInterestCrop", Style: &RegionOfInterestCropStyleVariableInline{Value: RegionOfInterestCropStylePtr("fill")}, Gravity: &GravityVariableInline{Value: GravityPtr("Center")}, - Width: &IntegerVariableInline{Value: tools.IntPtr(7)}, - Height: &IntegerVariableInline{Value: tools.IntPtr(8)}, + Width: &IntegerVariableInline{Value: ptr.To(7)}, + Height: &IntegerVariableInline{Value: ptr.To(8)}, RegionOfInterest: &RectangleShapeType{ Anchor: &PointShapeType{ - X: &NumberVariableInline{Value: tools.Float64Ptr(4)}, - Y: &NumberVariableInline{Value: tools.Float64Ptr(5)}, + X: &NumberVariableInline{Value: ptr.To(float64(4))}, + Y: &NumberVariableInline{Value: ptr.To(float64(5))}, }, - Width: &NumberVariableInline{Value: tools.Float64Ptr(8)}, - Height: &NumberVariableInline{Value: tools.Float64Ptr(9)}, + Width: &NumberVariableInline{Value: ptr.To(float64(8))}, + Height: &NumberVariableInline{Value: ptr.To(float64(9))}, }, }, &Composite{ Transformation: "Composite", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr(GravityNorthWest), @@ -1557,19 +1557,19 @@ func TestGetPolicy(t *testing.T) { Image: &TextImageType{ Type: "Text", Fill: &StringVariableInline{ - Value: tools.StringPtr("#000000"), + Value: ptr.To("#000000"), }, Size: &NumberVariableInline{ - Value: tools.Float64Ptr(72), + Value: ptr.To(float64(72)), }, Stroke: &StringVariableInline{ - Value: tools.StringPtr("#FFFFFF"), + Value: ptr.To("#FFFFFF"), }, StrokeSize: &NumberVariableInline{ - Value: tools.Float64Ptr(0), + Value: ptr.To(float64(0)), }, Text: &StringVariableInline{ - Value: tools.StringPtr("Hello There"), + Value: ptr.To("Hello There"), }, Transformation: &Compound{ Transformation: "Compound", @@ -1584,7 +1584,7 @@ func TestGetPolicy(t *testing.T) { Value: IfDimensionPostDimensionPtr("width"), }, Value: &IntegerVariableInline{ - Name: tools.StringPtr("MaxDimOld"), + Name: ptr.To("MaxDimOld"), }, Default: &CompoundPost{ Transformation: "Compound", @@ -1595,7 +1595,7 @@ func TestGetPolicy(t *testing.T) { Value: IfDimensionPostDimensionPtr("width"), }, Value: &IntegerVariableInline{ - Name: tools.StringPtr("MinDim"), + Name: ptr.To("MinDim"), }, LessThan: &CompoundPost{ Transformation: "Compound", @@ -1603,13 +1603,13 @@ func TestGetPolicy(t *testing.T) { &BackgroundColor{ Transformation: "BackgroundColor", Color: &StringVariableInline{ - Value: tools.StringPtr("#ffffff"), + Value: ptr.To("#ffffff"), }, }, &BackgroundColor{ Transformation: "BackgroundColor", Color: &StringVariableInline{ - Value: tools.StringPtr("#00ffff"), + Value: ptr.To("#00ffff"), }, }, }, @@ -1621,11 +1621,11 @@ func TestGetPolicy(t *testing.T) { &CompositePost{ Gravity: &GravityPostVariableInline{Value: GravityPostPtr("NorthWest")}, Image: &TextImageTypePost{ - Fill: &StringVariableInline{Value: tools.StringPtr("#000000")}, - Size: &NumberVariableInline{Value: tools.Float64Ptr(72)}, - Stroke: &StringVariableInline{Value: tools.StringPtr("#FFFFFF")}, - StrokeSize: &NumberVariableInline{Value: tools.Float64Ptr(0)}, - Text: &StringVariableInline{Value: tools.StringPtr("test")}, + Fill: &StringVariableInline{Value: ptr.To("#000000")}, + Size: &NumberVariableInline{Value: ptr.To(float64(72))}, + Stroke: &StringVariableInline{Value: ptr.To("#FFFFFF")}, + StrokeSize: &NumberVariableInline{Value: ptr.To(float64(0))}, + Text: &StringVariableInline{Value: ptr.To("test")}, Type: TextImageTypePostTypeText, Transformation: &CompoundPost{ Transformation: CompoundPostTransformationCompound, @@ -1633,11 +1633,11 @@ func TestGetPolicy(t *testing.T) { }, Placement: &CompositePostPlacementVariableInline{Value: CompositePostPlacementPtr(CompositePostPlacementOver)}, Transformation: CompositePostTransformationComposite, - XPosition: &IntegerVariableInline{Value: tools.IntPtr(0)}, - YPosition: &IntegerVariableInline{Value: tools.IntPtr(0)}, + XPosition: &IntegerVariableInline{Value: ptr.To(0)}, + YPosition: &IntegerVariableInline{Value: ptr.To(0)}, }, }, - Video: tools.BoolPtr(false), + Video: ptr.To(false), User: "jsmith", DateCreated: "2021-12-07 16:20:34+0000", }, @@ -1682,7 +1682,7 @@ func TestGetPolicy(t *testing.T) { Value: OutputVideoPerceptualQualityPtr(OutputVideoPerceptualQualityMediumHigh), }, }, - Video: tools.BoolPtr(true), + Video: ptr.To(true), User: "jsmith", DateCreated: "2022-01-24 20:17:10+0000", }, @@ -1862,10 +1862,10 @@ func TestPutPolicy(t *testing.T) { &Composite{ Transformation: "Composite", XPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, YPosition: &IntegerVariableInline{ - Value: tools.IntPtr(0), + Value: ptr.To(0), }, Gravity: &GravityVariableInline{ Value: GravityPtr(GravityNorthWest), @@ -1876,19 +1876,19 @@ func TestPutPolicy(t *testing.T) { Image: &TextImageType{ Type: "Text", Fill: &StringVariableInline{ - Value: tools.StringPtr("#000000"), + Value: ptr.To("#000000"), }, Size: &NumberVariableInline{ - Value: tools.Float64Ptr(72), + Value: ptr.To(float64(72)), }, Stroke: &StringVariableInline{ - Value: tools.StringPtr("#FFFFFF"), + Value: ptr.To("#FFFFFF"), }, StrokeSize: &NumberVariableInline{ - Value: tools.Float64Ptr(0), + Value: ptr.To(float64(0)), }, Text: &StringVariableInline{ - Value: tools.StringPtr("Hello There"), + Value: ptr.To("Hello There"), }, Transformation: &Compound{ Transformation: "Compound", diff --git a/pkg/imaging/policyset.go b/pkg/imaging/policyset.go index 5e846a56..ee10cc46 100644 --- a/pkg/imaging/policyset.go +++ b/pkg/imaging/policyset.go @@ -7,32 +7,12 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // PolicySets is an Image and Video Manager API interface for PolicySets - // - // See: https://techdocs.akamai.com/ivm/reference/api - PolicySets interface { - // ListPolicySets lists all PolicySets of specified type for the current account - ListPolicySets(context.Context, ListPolicySetsRequest) ([]PolicySet, error) - - // GetPolicySet gets specific PolicySet by PolicySetID - GetPolicySet(context.Context, GetPolicySetRequest) (*PolicySet, error) - - // CreatePolicySet creates configuration for an PolicySet - CreatePolicySet(context.Context, CreatePolicySetRequest) (*PolicySet, error) - - // UpdatePolicySet creates configuration for an PolicySet - UpdatePolicySet(context.Context, UpdatePolicySetRequest) (*PolicySet, error) - - // DeletePolicySet deletes configuration for an PolicySet - DeletePolicySet(context.Context, DeletePolicySetRequest) error - } - // ListPolicySetsRequest describes the parameters of the ListPolicySets request ListPolicySetsRequest struct { ContractID string diff --git a/pkg/networklists/activations.go b/pkg/networklists/activations.go index 9f1754cb..9be1e0ba 100644 --- a/pkg/networklists/activations.go +++ b/pkg/networklists/activations.go @@ -10,31 +10,6 @@ import ( ) type ( - // The Activations interface supports activating and deactivating network lists. - // - // https://techdocs.akamai.com/network-lists/reference/api - Activations interface { - // GetActivations retrieves list of network list activations. - // - // See: https://techdocs.akamai.com/network-lists/reference/get-network-list-status - GetActivations(ctx context.Context, params GetActivationsRequest) (*GetActivationsResponse, error) - - // GetActivation retrieves network list activation. - // - // See: https://techdocs.akamai.com/network-lists/reference/get-activation - GetActivation(ctx context.Context, params GetActivationRequest) (*GetActivationResponse, error) - - // CreateActivations activates network list. - // - // See: https://techdocs.akamai.com/network-lists/reference/post-network-list-activate - CreateActivations(ctx context.Context, params CreateActivationsRequest) (*CreateActivationsResponse, error) - - // RemoveActivations deactivates network list. - // - // See: https://techdocs.akamai.com/network-lists/reference/post-network-list-activate - RemoveActivations(ctx context.Context, params RemoveActivationsRequest) (*RemoveActivationsResponse, error) - } - // GetActivationsRequest contains request parameters for getting activation status GetActivationsRequest struct { UniqueID string `json:"-"` diff --git a/pkg/networklists/activations_test.go b/pkg/networklists/activations_test.go index 507f24dc..27932cf3 100644 --- a/pkg/networklists/activations_test.go +++ b/pkg/networklists/activations_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/networklists/errors.go b/pkg/networklists/errors.go index d6e0c900..b7a5a2e6 100644 --- a/pkg/networklists/errors.go +++ b/pkg/networklists/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) var ( diff --git a/pkg/networklists/errors_test.go b/pkg/networklists/errors_test.go index c49cb97e..011e9acc 100644 --- a/pkg/networklists/errors_test.go +++ b/pkg/networklists/errors_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) diff --git a/pkg/networklists/network_list.go b/pkg/networklists/network_list.go index 52456e8f..8622b9b0 100644 --- a/pkg/networklists/network_list.go +++ b/pkg/networklists/network_list.go @@ -10,34 +10,6 @@ import ( ) type ( - // The NetworkList interface supports creating, retrieving, modifying and removing network lists. - NetworkList interface { - // GetNetworkLists lists all network lists available for an authenticated user. - // - // See: https://techdocs.akamai.com/network-lists/reference/get-network-lists - GetNetworkLists(ctx context.Context, params GetNetworkListsRequest) (*GetNetworkListsResponse, error) - - // GetNetworkList retrieves network list with specific network list id. - // - // See: https://techdocs.akamai.com/network-lists/reference/get-network-list - GetNetworkList(ctx context.Context, params GetNetworkListRequest) (*GetNetworkListResponse, error) - - // CreateNetworkList creates a new network list. - // - // See: https://techdocs.akamai.com/network-lists/reference/post-network-lists - CreateNetworkList(ctx context.Context, params CreateNetworkListRequest) (*CreateNetworkListResponse, error) - - // UpdateNetworkList modifies the network list. - // - //See: https://techdocs.akamai.com/network-lists/reference/put-network-list - UpdateNetworkList(ctx context.Context, params UpdateNetworkListRequest) (*UpdateNetworkListResponse, error) - - // RemoveNetworkList removes a network list. - // - // See: https://techdocs.akamai.com/network-lists/reference/delete-network-list - RemoveNetworkList(ctx context.Context, params RemoveNetworkListRequest) (*RemoveNetworkListResponse, error) - } - // GetNetworkListRequest contains request parameters for GetNetworkList method GetNetworkListRequest struct { UniqueID string `json:"-"` diff --git a/pkg/networklists/network_list_description.go b/pkg/networklists/network_list_description.go index 1ab2233a..9f1f2bcd 100644 --- a/pkg/networklists/network_list_description.go +++ b/pkg/networklists/network_list_description.go @@ -9,19 +9,6 @@ import ( ) type ( - // The NetworkListDescription interface supports retrieving and updating a network list's description. - NetworkListDescription interface { - // GetNetworkListDescription retrieves network list with description. - // - // See: https://techdocs.akamai.com/network-lists/reference/get-network-list - GetNetworkListDescription(ctx context.Context, params GetNetworkListDescriptionRequest) (*GetNetworkListDescriptionResponse, error) - - // UpdateNetworkListDescription modifies network list description. - // - // See: https://techdocs.akamai.com/network-lists/reference/put-network-list-details - UpdateNetworkListDescription(ctx context.Context, params UpdateNetworkListDescriptionRequest) (*UpdateNetworkListDescriptionResponse, error) - } - // GetNetworkListDescriptionRequest contains request parameters for GetNetworkListDescription method GetNetworkListDescriptionRequest struct { UniqueID string `json:"uniqueId"` diff --git a/pkg/networklists/network_list_description_test.go b/pkg/networklists/network_list_description_test.go index a2487030..ade94e08 100644 --- a/pkg/networklists/network_list_description_test.go +++ b/pkg/networklists/network_list_description_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/networklists/network_list_subscription.go b/pkg/networklists/network_list_subscription.go index dd6120b8..b02824f2 100644 --- a/pkg/networklists/network_list_subscription.go +++ b/pkg/networklists/network_list_subscription.go @@ -7,24 +7,6 @@ import ( ) type ( - // The NetworkListSubscription interface supports creating, modifying and removing network list subscriptions. - NetworkListSubscription interface { - // GetNetworkListSubscription retrieves networklist subscription. - // - // See: https://techdocs.akamai.com/network-lists/reference/post-notifications-subscribe - GetNetworkListSubscription(ctx context.Context, params GetNetworkListSubscriptionRequest) (*GetNetworkListSubscriptionResponse, error) - - // UpdateNetworkListSubscription updates networklist subscription. - // - // See: https://techdocs.akamai.com/network-lists/reference/post-notifications-subscribe - UpdateNetworkListSubscription(ctx context.Context, params UpdateNetworkListSubscriptionRequest) (*UpdateNetworkListSubscriptionResponse, error) - - // RemoveNetworkListSubscription unsubscribes networklist. - // - // See: https://techdocs.akamai.com/network-lists/reference/post-notifications-unsubscribe - RemoveNetworkListSubscription(ctx context.Context, params RemoveNetworkListSubscriptionRequest) (*RemoveNetworkListSubscriptionResponse, error) - } - // GetNetworkListSubscriptionRequest contains request parameters for GetNetworkListSubscription GetNetworkListSubscriptionRequest struct { Recipients []string `json:"-"` diff --git a/pkg/networklists/network_list_subscription_test.go b/pkg/networklists/network_list_subscription_test.go index 215ef255..e2367aff 100644 --- a/pkg/networklists/network_list_subscription_test.go +++ b/pkg/networklists/network_list_subscription_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/networklists/network_list_test.go b/pkg/networklists/network_list_test.go index 27b5ffcf..7a77addb 100644 --- a/pkg/networklists/network_list_test.go +++ b/pkg/networklists/network_list_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/networklists/networklists.go b/pkg/networklists/networklists.go index 752a5044..2030717e 100644 --- a/pkg/networklists/networklists.go +++ b/pkg/networklists/networklists.go @@ -4,9 +4,10 @@ package networklists import ( + "context" "errors" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" ) var ( @@ -15,12 +16,85 @@ var ( ) type ( - // NTWRKLISTS is the networklist api interface - NTWRKLISTS interface { - Activations - NetworkList - NetworkListDescription - NetworkListSubscription + // NetworkList is the networklist api interface + NetworkList interface { + // Activations + + // GetActivations retrieves list of network list activations. + // + // See: https://techdocs.akamai.com/network-lists/reference/get-network-list-status + GetActivations(ctx context.Context, params GetActivationsRequest) (*GetActivationsResponse, error) + + // GetActivation retrieves network list activation. + // + // See: https://techdocs.akamai.com/network-lists/reference/get-activation + GetActivation(ctx context.Context, params GetActivationRequest) (*GetActivationResponse, error) + + // CreateActivations activates network list. + // + // See: https://techdocs.akamai.com/network-lists/reference/post-network-list-activate + CreateActivations(ctx context.Context, params CreateActivationsRequest) (*CreateActivationsResponse, error) + + // RemoveActivations deactivates network list. + // + // See: https://techdocs.akamai.com/network-lists/reference/post-network-list-activate + RemoveActivations(ctx context.Context, params RemoveActivationsRequest) (*RemoveActivationsResponse, error) + + // NetworkList + + // GetNetworkLists lists all network lists available for an authenticated user. + // + // See: https://techdocs.akamai.com/network-lists/reference/get-network-lists + GetNetworkLists(ctx context.Context, params GetNetworkListsRequest) (*GetNetworkListsResponse, error) + + // GetNetworkList retrieves network list with specific network list id. + // + // See: https://techdocs.akamai.com/network-lists/reference/get-network-list + GetNetworkList(ctx context.Context, params GetNetworkListRequest) (*GetNetworkListResponse, error) + + // CreateNetworkList creates a new network list. + // + // See: https://techdocs.akamai.com/network-lists/reference/post-network-lists + CreateNetworkList(ctx context.Context, params CreateNetworkListRequest) (*CreateNetworkListResponse, error) + + // UpdateNetworkList modifies the network list. + // + //See: https://techdocs.akamai.com/network-lists/reference/put-network-list + UpdateNetworkList(ctx context.Context, params UpdateNetworkListRequest) (*UpdateNetworkListResponse, error) + + // RemoveNetworkList removes a network list. + // + // See: https://techdocs.akamai.com/network-lists/reference/delete-network-list + RemoveNetworkList(ctx context.Context, params RemoveNetworkListRequest) (*RemoveNetworkListResponse, error) + + // NetworkListDescription + + // GetNetworkListDescription retrieves network list with description. + // + // See: https://techdocs.akamai.com/network-lists/reference/get-network-list + GetNetworkListDescription(ctx context.Context, params GetNetworkListDescriptionRequest) (*GetNetworkListDescriptionResponse, error) + + // UpdateNetworkListDescription modifies network list description. + // + // See: https://techdocs.akamai.com/network-lists/reference/put-network-list-details + UpdateNetworkListDescription(ctx context.Context, params UpdateNetworkListDescriptionRequest) (*UpdateNetworkListDescriptionResponse, error) + + // NetworkListSubscription + + // GetNetworkListSubscription retrieves networklist subscription. + // + // See: https://techdocs.akamai.com/network-lists/reference/post-notifications-subscribe + GetNetworkListSubscription(ctx context.Context, params GetNetworkListSubscriptionRequest) (*GetNetworkListSubscriptionResponse, error) + + // UpdateNetworkListSubscription updates networklist subscription. + // + // See: https://techdocs.akamai.com/network-lists/reference/post-notifications-subscribe + UpdateNetworkListSubscription(ctx context.Context, params UpdateNetworkListSubscriptionRequest) (*UpdateNetworkListSubscriptionResponse, error) + + // RemoveNetworkListSubscription unsubscribes networklist. + // + // See: https://techdocs.akamai.com/network-lists/reference/post-notifications-unsubscribe + RemoveNetworkListSubscription(ctx context.Context, params RemoveNetworkListSubscriptionRequest) (*RemoveNetworkListSubscriptionResponse, error) } networklists struct { @@ -32,11 +106,11 @@ type ( Option func(*networklists) // ClientFunc is a networklist client new method, this can used for mocking - ClientFunc func(sess session.Session, opts ...Option) NTWRKLISTS + ClientFunc func(sess session.Session, opts ...Option) NetworkList ) // Client returns a new networklist Client instance with the specified controller -func Client(sess session.Session, opts ...Option) NTWRKLISTS { +func Client(sess session.Session, opts ...Option) NetworkList { p := &networklists{ Session: sess, } diff --git a/pkg/networklists/networklists_test.go b/pkg/networklists/networklists_test.go index 4e9a9a2d..f1e4ba1c 100644 --- a/pkg/networklists/networklists_test.go +++ b/pkg/networklists/networklists_test.go @@ -12,13 +12,13 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func mockAPIClient(t *testing.T, mockServer *httptest.Server) NTWRKLISTS { +func mockAPIClient(t *testing.T, mockServer *httptest.Server) NetworkList { serverURL, err := url.Parse(mockServer.URL) require.NoError(t, err) certPool := x509.NewCertPool() diff --git a/pkg/papi/activation.go b/pkg/papi/activation.go index eeba52c1..80f3d6ab 100644 --- a/pkg/papi/activation.go +++ b/pkg/papi/activation.go @@ -12,29 +12,6 @@ import ( ) type ( - // Activations contains operations available on Activation resource - Activations interface { - // CreateActivation creates a new activation or deactivation request - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-property-activations - CreateActivation(context.Context, CreateActivationRequest) (*CreateActivationResponse, error) - - // GetActivations returns a list of the property activations - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-activations - GetActivations(ctx context.Context, params GetActivationsRequest) (*GetActivationsResponse, error) - - // GetActivation gets details about an activation - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-activation - GetActivation(context.Context, GetActivationRequest) (*GetActivationResponse, error) - - // CancelActivation allows for canceling an activation while it is still PENDING - // - // https://techdocs.akamai.com/property-mgr/reference/delete-property-activation - CancelActivation(context.Context, CancelActivationRequest) (*CancelActivationResponse, error) - } - // ActivationFallbackInfo encapsulates information about fast fallback, which may allow you to fallback to a previous activation when // POSTing an activation with useFastFallback enabled. ActivationFallbackInfo struct { diff --git a/pkg/papi/clientsettings.go b/pkg/papi/clientsettings.go index 9bb73c3d..053ad5ea 100644 --- a/pkg/papi/clientsettings.go +++ b/pkg/papi/clientsettings.go @@ -8,19 +8,6 @@ import ( ) type ( - // ClientSettings contains operations available on ClientSettings resource - ClientSettings interface { - // GetClientSettings returns client's settings. - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-client-settings - GetClientSettings(context.Context) (*ClientSettingsBody, error) - - // UpdateClientSettings updates client's settings. - // - // See: https://techdocs.akamai.com/property-mgr/reference/put-client-settings - UpdateClientSettings(context.Context, ClientSettingsBody) (*ClientSettingsBody, error) - } - // ClientSettingsBody represents both the request and response bodies for operating on client settings resource ClientSettingsBody struct { RuleFormat string `json:"ruleFormat"` diff --git a/pkg/papi/contract.go b/pkg/papi/contract.go index 70969c55..3015b1a9 100644 --- a/pkg/papi/contract.go +++ b/pkg/papi/contract.go @@ -8,14 +8,6 @@ import ( ) type ( - // Contracts contains operations available on Contract resource - Contracts interface { - // GetContracts provides a read-only list of contract names and identifiers - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-contracts - GetContracts(context.Context) (*GetContractsResponse, error) - } - // Contract represents a property contract resource Contract struct { ContractID string `json:"contractId"` diff --git a/pkg/papi/cpcode.go b/pkg/papi/cpcode.go index eccf5116..da9b5a0a 100644 --- a/pkg/papi/cpcode.go +++ b/pkg/papi/cpcode.go @@ -10,34 +10,6 @@ import ( ) type ( - // CPCodes contains operations available on CPCode resource - CPCodes interface { - // GetCPCodes lists all available CP codes - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-cpcodes - GetCPCodes(context.Context, GetCPCodesRequest) (*GetCPCodesResponse, error) - - // GetCPCode gets the CP code with provided ID - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-cpcode - GetCPCode(context.Context, GetCPCodeRequest) (*GetCPCodesResponse, error) - - // GetCPCodeDetail lists detailed information about a specific CP code - // - // See: https://techdocs.akamai.com/cp-codes/reference/get-cpcode - GetCPCodeDetail(context.Context, int) (*CPCodeDetailResponse, error) - - // CreateCPCode creates a new CP code - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-cpcodes - CreateCPCode(context.Context, CreateCPCodeRequest) (*CreateCPCodeResponse, error) - - // UpdateCPCode modifies a specific CP code. You should only modify a CP code's name, time zone, and purgeable member - // - // See: https://techdocs.akamai.com/cp-codes/reference/put-cpcode - UpdateCPCode(context.Context, UpdateCPCodeRequest) (*CPCodeDetailResponse, error) - } - // CPCode contains CP code resource data CPCode struct { ID string `json:"cpcodeId"` diff --git a/pkg/papi/cpcode_test.go b/pkg/papi/cpcode_test.go index 32f70e08..6668b186 100644 --- a/pkg/papi/cpcode_test.go +++ b/pkg/papi/cpcode_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -724,7 +724,7 @@ func TestUpdateCPCode(t *testing.T) { params: UpdateCPCodeRequest{ ID: 123, Name: "test-cp-code", - Purgeable: tools.BoolPtr(false), + Purgeable: ptr.To(false), Contracts: []CPCodeContract{ { ContractID: "test-contract-id", diff --git a/pkg/papi/edgehostname.go b/pkg/papi/edgehostname.go index 6fce8a78..a6707108 100644 --- a/pkg/papi/edgehostname.go +++ b/pkg/papi/edgehostname.go @@ -7,29 +7,11 @@ import ( "net/http" "strings" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // EdgeHostnames contains operations available on EdgeHostnames resource - EdgeHostnames interface { - // GetEdgeHostnames fetches a list of edge hostnames - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-edgehostnames - GetEdgeHostnames(context.Context, GetEdgeHostnamesRequest) (*GetEdgeHostnamesResponse, error) - - // GetEdgeHostname fetches edge hostname with given ID - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-edgehostname - GetEdgeHostname(context.Context, GetEdgeHostnameRequest) (*GetEdgeHostnamesResponse, error) - - // CreateEdgeHostname creates a new edge hostname - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-edgehostnames - CreateEdgeHostname(context.Context, CreateEdgeHostnameRequest) (*CreateEdgeHostnameResponse, error) - } - // GetEdgeHostnamesRequest contains query params used for listing edge hostnames GetEdgeHostnamesRequest struct { ContractID string diff --git a/pkg/papi/errors.go b/pkg/papi/errors.go index 5e04a1da..add7767b 100644 --- a/pkg/papi/errors.go +++ b/pkg/papi/errors.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/errs" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/errs" ) type ( diff --git a/pkg/papi/errors_test.go b/pkg/papi/errors_test.go index 94b53934..15e6d207 100644 --- a/pkg/papi/errors_test.go +++ b/pkg/papi/errors_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -95,7 +95,7 @@ func TestErrorIs(t *testing.T) { err: Error{ StatusCode: http.StatusTooManyRequests, LimitKey: "DEFAULT_CERTS_PER_CONTRACT", - Remaining: tools.IntPtr(0), + Remaining: ptr.To(0), }, given: ErrDefaultCertLimitReached, expected: true, @@ -104,7 +104,7 @@ func TestErrorIs(t *testing.T) { err: Error{ StatusCode: http.StatusTooManyRequests, LimitKey: "DEFAULT_CERTS_PER_CONTRACT", - Remaining: tools.IntPtr(0), + Remaining: ptr.To(0), }, given: ErrSBDNotEnabled, expected: false, diff --git a/pkg/papi/group.go b/pkg/papi/group.go index d47fe834..1d35cee6 100644 --- a/pkg/papi/group.go +++ b/pkg/papi/group.go @@ -8,14 +8,6 @@ import ( ) type ( - // Groups contains operations available on Group resource - Groups interface { - // GetGroups provides a read-only list of groups, which may contain properties. - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-groups - GetGroups(context.Context) (*GetGroupsResponse, error) - } - // Group represents a property group resource Group struct { GroupID string `json:"groupId"` diff --git a/pkg/papi/include.go b/pkg/papi/include.go index 47c73df2..b11fa11d 100644 --- a/pkg/papi/include.go +++ b/pkg/papi/include.go @@ -7,40 +7,12 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Includes contains operations available on Include resource - Includes interface { - // ListIncludes lists Includes available for the current contract and group - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-includes - ListIncludes(context.Context, ListIncludesRequest) (*ListIncludesResponse, error) - - // ListIncludeParents lists parents of a specific Include - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-parents - ListIncludeParents(context.Context, ListIncludeParentsRequest) (*ListIncludeParentsResponse, error) - - // GetInclude gets information about a specific Include - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include - GetInclude(context.Context, GetIncludeRequest) (*GetIncludeResponse, error) - - // CreateInclude creates a new Include - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-includes - CreateInclude(context.Context, CreateIncludeRequest) (*CreateIncludeResponse, error) - - // DeleteInclude deletes an Include - // - // See: https://techdocs.akamai.com/property-mgr/reference/delete-include - DeleteInclude(context.Context, DeleteIncludeRequest) (*DeleteIncludeResponse, error) - } - // ListIncludesRequest contains parameters used to list includes ListIncludesRequest struct { ContractID string diff --git a/pkg/papi/include_activations.go b/pkg/papi/include_activations.go index 09a68eb0..5df617cd 100644 --- a/pkg/papi/include_activations.go +++ b/pkg/papi/include_activations.go @@ -9,40 +9,12 @@ import ( "net/url" "strings" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // IncludeActivations contains operations available on IncludeVersion resource - IncludeActivations interface { - // ActivateInclude creates a new include activation, which deactivates any current activation - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation - ActivateInclude(context.Context, ActivateIncludeRequest) (*ActivationIncludeResponse, error) - - // DeactivateInclude deactivates the include activation - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation - DeactivateInclude(context.Context, DeactivateIncludeRequest) (*DeactivationIncludeResponse, error) - - // CancelIncludeActivation cancels specified include activation, if it is still in `PENDING` state - // - // See: https://techdocs.akamai.com/property-mgr/reference/delete-include-activation - CancelIncludeActivation(context.Context, CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error) - - // GetIncludeActivation gets details about an activation - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-activation - GetIncludeActivation(context.Context, GetIncludeActivationRequest) (*GetIncludeActivationResponse, error) - - // ListIncludeActivations lists all activations for all versions of the include, on both production and staging networks - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-activations - ListIncludeActivations(context.Context, ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) - } - // ActivateIncludeRequest contains parameters used to activate include ActivateIncludeRequest ActivateOrDeactivateIncludeRequest @@ -378,7 +350,7 @@ func (p *papi) ActivateInclude(ctx context.Context, params ActivateIncludeReques } if params.IgnoreHTTPErrors == nil { - params.IgnoreHTTPErrors = tools.BoolPtr(true) + params.IgnoreHTTPErrors = ptr.To(true) } requestBody := struct { @@ -424,7 +396,7 @@ func (p *papi) DeactivateInclude(ctx context.Context, params DeactivateIncludeRe } if params.IgnoreHTTPErrors == nil { - params.IgnoreHTTPErrors = tools.BoolPtr(true) + params.IgnoreHTTPErrors = ptr.To(true) } requestBody := struct { diff --git a/pkg/papi/include_rule.go b/pkg/papi/include_rule.go index 7fcdd21a..ee72c51e 100644 --- a/pkg/papi/include_rule.go +++ b/pkg/papi/include_rule.go @@ -8,24 +8,11 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // IncludeRules contains operations available on IncludeRule resource - IncludeRules interface { - // GetIncludeRuleTree gets the entire rule tree for an include version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-version-rules - GetIncludeRuleTree(context.Context, GetIncludeRuleTreeRequest) (*GetIncludeRuleTreeResponse, error) - - // UpdateIncludeRuleTree updates the rule tree for an include version - // - // See: https://techdocs.akamai.com/property-mgr/reference/patch-include-version-rules - UpdateIncludeRuleTree(context.Context, UpdateIncludeRuleTreeRequest) (*UpdateIncludeRuleTreeResponse, error) - } - // GetIncludeRuleTreeRequest contains path and query params necessary to perform GetIncludeRuleTree GetIncludeRuleTreeRequest struct { ContractID string diff --git a/pkg/papi/include_rule_test.go b/pkg/papi/include_rule_test.go index b47ed227..eaf3ee98 100644 --- a/pkg/papi/include_rule_test.go +++ b/pkg/papi/include_rule_test.go @@ -8,8 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -185,11 +184,11 @@ func TestGetIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -358,11 +357,11 @@ func TestGetIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -562,11 +561,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -783,11 +782,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { TemplateLink: "/platformtoolkit/service/ruletemplate/30582260/1?accountId=1-1TJZFB&gid=61726&ck=16.3.1.1", Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -863,11 +862,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -958,11 +957,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1039,11 +1038,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1120,11 +1119,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1201,11 +1200,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1283,11 +1282,11 @@ func TestUpdateIncludeRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, diff --git a/pkg/papi/include_test.go b/pkg/papi/include_test.go index d47b2658..645ece8f 100644 --- a/pkg/papi/include_test.go +++ b/pkg/papi/include_test.go @@ -8,8 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -83,7 +82,7 @@ func TestListIncludes(t *testing.T) { IncludeName: "test_include_1", IncludeType: IncludeTypeCommonSettings, LatestVersion: 1, - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), }, }, }, @@ -148,7 +147,7 @@ func TestListIncludes(t *testing.T) { IncludeName: "test_include_1", IncludeType: IncludeTypeCommonSettings, LatestVersion: 1, - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), }, }, }, @@ -262,7 +261,7 @@ func TestListIncludeParents(t *testing.T) { GroupID: "test_group", PropertyID: "prp_123456", PropertyName: "test_property", - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), }, }, }, @@ -301,7 +300,7 @@ func TestListIncludeParents(t *testing.T) { GroupID: "test_group", PropertyID: "prp_123456", PropertyName: "test_property", - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), }, }, }, @@ -423,7 +422,7 @@ func TestGetInclude(t *testing.T) { IncludeName: "test_include", IncludeType: "MICROSERVICES", LatestVersion: 1, - PropertyType: tools.StringPtr("INCLUDE"), + PropertyType: ptr.To("INCLUDE"), }, }, }, @@ -436,7 +435,7 @@ func TestGetInclude(t *testing.T) { IncludeName: "test_include", IncludeType: "MICROSERVICES", LatestVersion: 1, - PropertyType: tools.StringPtr("INCLUDE"), + PropertyType: ptr.To("INCLUDE"), }, }, }, diff --git a/pkg/papi/include_versions.go b/pkg/papi/include_versions.go index e0a6b829..eeafea0d 100644 --- a/pkg/papi/include_versions.go +++ b/pkg/papi/include_versions.go @@ -8,39 +8,11 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // IncludeVersions contains operations available on IncludeVersion resource - IncludeVersions interface { - // CreateIncludeVersion creates a new include version based on any previous version - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-include-versions - CreateIncludeVersion(context.Context, CreateIncludeVersionRequest) (*CreateIncludeVersionResponse, error) - - // GetIncludeVersion polls the state of a specific include version, for example to check its activation status - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-version - GetIncludeVersion(context.Context, GetIncludeVersionRequest) (*GetIncludeVersionResponse, error) - - // ListIncludeVersions lists the include versions, with results limited to the 500 most recent versions - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-versions - ListIncludeVersions(context.Context, ListIncludeVersionsRequest) (*ListIncludeVersionsResponse, error) - - // ListIncludeVersionAvailableCriteria lists available criteria for the include version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-available-criteria - ListIncludeVersionAvailableCriteria(context.Context, ListAvailableCriteriaRequest) (*AvailableCriteriaResponse, error) - - // ListIncludeVersionAvailableBehaviors lists available behaviors for the include version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-include-available-behaviors - ListIncludeVersionAvailableBehaviors(context.Context, ListAvailableBehaviorsRequest) (*AvailableBehaviorsResponse, error) - } - // CreateIncludeVersionRequest contains parameters used to create a new include version CreateIncludeVersionRequest struct { IncludeID string diff --git a/pkg/papi/papi.go b/pkg/papi/papi.go index 6c31b961..160293bd 100644 --- a/pkg/papi/papi.go +++ b/pkg/papi/papi.go @@ -2,10 +2,11 @@ package papi import ( + "context" "errors" "net/http" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/spf13/cast" ) @@ -29,23 +30,304 @@ var ( type ( // PAPI is the papi api interface PAPI interface { - Activations - ClientSettings - Contracts - CPCodes - EdgeHostnames - Groups - Includes - IncludeRules - IncludeActivations - IncludeVersions - Products - Properties - PropertyRules - PropertyVersionHostnames - PropertyVersions - RuleFormats - Search + // Activations + + // CreateActivation creates a new activation or deactivation request + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-property-activations + CreateActivation(context.Context, CreateActivationRequest) (*CreateActivationResponse, error) + + // GetActivations returns a list of the property activations + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-activations + GetActivations(ctx context.Context, params GetActivationsRequest) (*GetActivationsResponse, error) + + // GetActivation gets details about an activation + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-activation + GetActivation(context.Context, GetActivationRequest) (*GetActivationResponse, error) + + // CancelActivation allows for canceling an activation while it is still PENDING + // + // https://techdocs.akamai.com/property-mgr/reference/delete-property-activation + CancelActivation(context.Context, CancelActivationRequest) (*CancelActivationResponse, error) + + // ClientSettings + + // GetClientSettings returns client's settings. + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-client-settings + GetClientSettings(context.Context) (*ClientSettingsBody, error) + + // UpdateClientSettings updates client's settings. + // + // See: https://techdocs.akamai.com/property-mgr/reference/put-client-settings + UpdateClientSettings(context.Context, ClientSettingsBody) (*ClientSettingsBody, error) + + // Contracts + + // GetContracts provides a read-only list of contract names and identifiers + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-contracts + GetContracts(context.Context) (*GetContractsResponse, error) + + // CPCodes + + // GetCPCodes lists all available CP codes + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-cpcodes + GetCPCodes(context.Context, GetCPCodesRequest) (*GetCPCodesResponse, error) + + // GetCPCode gets the CP code with provided ID + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-cpcode + GetCPCode(context.Context, GetCPCodeRequest) (*GetCPCodesResponse, error) + + // GetCPCodeDetail lists detailed information about a specific CP code + // + // See: https://techdocs.akamai.com/cp-codes/reference/get-cpcode + GetCPCodeDetail(context.Context, int) (*CPCodeDetailResponse, error) + + // CreateCPCode creates a new CP code + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-cpcodes + CreateCPCode(context.Context, CreateCPCodeRequest) (*CreateCPCodeResponse, error) + + // UpdateCPCode modifies a specific CP code. You should only modify a CP code's name, time zone, and purgeable member + // + // See: https://techdocs.akamai.com/cp-codes/reference/put-cpcode + UpdateCPCode(context.Context, UpdateCPCodeRequest) (*CPCodeDetailResponse, error) + + // EdgeHostnames + + // GetEdgeHostnames fetches a list of edge hostnames + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-edgehostnames + GetEdgeHostnames(context.Context, GetEdgeHostnamesRequest) (*GetEdgeHostnamesResponse, error) + + // GetEdgeHostname fetches edge hostname with given ID + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-edgehostname + GetEdgeHostname(context.Context, GetEdgeHostnameRequest) (*GetEdgeHostnamesResponse, error) + + // CreateEdgeHostname creates a new edge hostname + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-edgehostnames + CreateEdgeHostname(context.Context, CreateEdgeHostnameRequest) (*CreateEdgeHostnameResponse, error) + + // Groups + + // GetGroups provides a read-only list of groups, which may contain properties. + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-groups + GetGroups(context.Context) (*GetGroupsResponse, error) + + // Includes + + // ListIncludes lists Includes available for the current contract and group + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-includes + ListIncludes(context.Context, ListIncludesRequest) (*ListIncludesResponse, error) + + // ListIncludeParents lists parents of a specific Include + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-parents + ListIncludeParents(context.Context, ListIncludeParentsRequest) (*ListIncludeParentsResponse, error) + + // GetInclude gets information about a specific Include + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include + GetInclude(context.Context, GetIncludeRequest) (*GetIncludeResponse, error) + + // CreateInclude creates a new Include + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-includes + CreateInclude(context.Context, CreateIncludeRequest) (*CreateIncludeResponse, error) + + // DeleteInclude deletes an Include + // + // See: https://techdocs.akamai.com/property-mgr/reference/delete-include + DeleteInclude(context.Context, DeleteIncludeRequest) (*DeleteIncludeResponse, error) + + // IncludeRules + + // GetIncludeRuleTree gets the entire rule tree for an include version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-version-rules + GetIncludeRuleTree(context.Context, GetIncludeRuleTreeRequest) (*GetIncludeRuleTreeResponse, error) + + // UpdateIncludeRuleTree updates the rule tree for an include version + // + // See: https://techdocs.akamai.com/property-mgr/reference/patch-include-version-rules + UpdateIncludeRuleTree(context.Context, UpdateIncludeRuleTreeRequest) (*UpdateIncludeRuleTreeResponse, error) + + // IncludeActivations + + // ActivateInclude creates a new include activation, which deactivates any current activation + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation + ActivateInclude(context.Context, ActivateIncludeRequest) (*ActivationIncludeResponse, error) + + // DeactivateInclude deactivates the include activation + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation + DeactivateInclude(context.Context, DeactivateIncludeRequest) (*DeactivationIncludeResponse, error) + + // CancelIncludeActivation cancels specified include activation, if it is still in `PENDING` state + // + // See: https://techdocs.akamai.com/property-mgr/reference/delete-include-activation + CancelIncludeActivation(context.Context, CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error) + + // GetIncludeActivation gets details about an activation + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-activation + GetIncludeActivation(context.Context, GetIncludeActivationRequest) (*GetIncludeActivationResponse, error) + + // ListIncludeActivations lists all activations for all versions of the include, on both production and staging networks + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-activations + ListIncludeActivations(context.Context, ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) + + // IncludeVersions + + // CreateIncludeVersion creates a new include version based on any previous version + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-include-versions + CreateIncludeVersion(context.Context, CreateIncludeVersionRequest) (*CreateIncludeVersionResponse, error) + + // GetIncludeVersion polls the state of a specific include version, for example to check its activation status + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-version + GetIncludeVersion(context.Context, GetIncludeVersionRequest) (*GetIncludeVersionResponse, error) + + // ListIncludeVersions lists the include versions, with results limited to the 500 most recent versions + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-versions + ListIncludeVersions(context.Context, ListIncludeVersionsRequest) (*ListIncludeVersionsResponse, error) + + // ListIncludeVersionAvailableCriteria lists available criteria for the include version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-available-criteria + ListIncludeVersionAvailableCriteria(context.Context, ListAvailableCriteriaRequest) (*AvailableCriteriaResponse, error) + + // ListIncludeVersionAvailableBehaviors lists available behaviors for the include version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-include-available-behaviors + ListIncludeVersionAvailableBehaviors(context.Context, ListAvailableBehaviorsRequest) (*AvailableBehaviorsResponse, error) + + // Products + + // GetProducts lists all available Products + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-products + GetProducts(context.Context, GetProductsRequest) (*GetProductsResponse, error) + + // Properties + + // GetProperties lists properties available for the current contract and group + // + // https://techdocs.akamai.com/property-mgr/reference/get-properties + GetProperties(ctx context.Context, r GetPropertiesRequest) (*GetPropertiesResponse, error) + + // CreateProperty creates a new property from scratch or bases one on another property's rule tree and optionally its set of assigned hostnames + // + // https://techdocs.akamai.com/property-mgr/reference/post-properties + CreateProperty(ctx context.Context, params CreatePropertyRequest) (*CreatePropertyResponse, error) + + // GetProperty gets a specific property + // + // https://techdocs.akamai.com/property-mgr/reference/get-property + GetProperty(ctx context.Context, params GetPropertyRequest) (*GetPropertyResponse, error) + + // RemoveProperty removes a specific property, which you can only do if none of its versions are currently active + // + // https://techdocs.akamai.com/property-mgr/reference/delete-property + RemoveProperty(ctx context.Context, params RemovePropertyRequest) (*RemovePropertyResponse, error) + + // MapPropertyNameToID returns (PAPI) property ID for given property name + // Mainly to be used to map (IAM) Property ID to (PAPI) Property ID + // To get property name for the mapping, please use iam.MapPropertyIDToName + MapPropertyNameToID(context.Context, MapPropertyNameToIDRequest) (*string, error) + + // PropertyRules + + // GetRuleTree gets the entire rule tree for a property version. + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-rules + GetRuleTree(context.Context, GetRuleTreeRequest) (*GetRuleTreeResponse, error) + + // UpdateRuleTree updates the rule tree for a property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/put-property-version-rules + UpdateRuleTree(context.Context, UpdateRulesRequest) (*UpdateRulesResponse, error) + + // PropertyVersionHostnames + + // GetPropertyVersionHostnames lists all the hostnames assigned to a property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-hostnames + GetPropertyVersionHostnames(context.Context, GetPropertyVersionHostnamesRequest) (*GetPropertyVersionHostnamesResponse, error) + + // UpdatePropertyVersionHostnames modifies the set of hostnames for a property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/patch-property-version-hostnames + UpdatePropertyVersionHostnames(context.Context, UpdatePropertyVersionHostnamesRequest) (*UpdatePropertyVersionHostnamesResponse, error) + + // PropertyVersions + + // GetPropertyVersions fetches available property versions + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-versions + GetPropertyVersions(context.Context, GetPropertyVersionsRequest) (*GetPropertyVersionsResponse, error) + + // GetPropertyVersion fetches specific property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version + GetPropertyVersion(context.Context, GetPropertyVersionRequest) (*GetPropertyVersionsResponse, error) + + // CreatePropertyVersion creates a new property version and returns location and number for the new version + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-property-versions + CreatePropertyVersion(context.Context, CreatePropertyVersionRequest) (*CreatePropertyVersionResponse, error) + + // GetLatestVersion fetches the latest property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-latest-property-version + GetLatestVersion(context.Context, GetLatestVersionRequest) (*GetPropertyVersionsResponse, error) + + // GetAvailableBehaviors fetches a list of available behaviors for given property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-available-behaviors + GetAvailableBehaviors(context.Context, GetAvailableBehaviorsRequest) (*GetBehaviorsResponse, error) + + // GetAvailableCriteria fetches a list of available criteria for given property version + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-available-criteria + GetAvailableCriteria(context.Context, GetAvailableCriteriaRequest) (*GetCriteriaResponse, error) + + // ListAvailableIncludes lists external resources that can be applied within a property version's rules + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-external-resources + ListAvailableIncludes(context.Context, ListAvailableIncludesRequest) (*ListAvailableIncludesResponse, error) + + // ListReferencedIncludes lists referenced includes for parent property + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-includes + ListReferencedIncludes(context.Context, ListReferencedIncludesRequest) (*ListReferencedIncludesResponse, error) + + // RuleFormats + + // GetRuleFormats provides a list of rule formats + // + // See: https://techdocs.akamai.com/property-mgr/reference/get-rule-formats + GetRuleFormats(context.Context) (*GetRuleFormatsResponse, error) + + // Search + + // SearchProperties searches properties by name, or by the hostname or edge hostname for which it’s currently active + // + // See: https://techdocs.akamai.com/property-mgr/reference/post-search-find-by-value + SearchProperties(context.Context, SearchRequest) (*SearchResponse, error) } papi struct { diff --git a/pkg/papi/papi_test.go b/pkg/papi/papi_test.go index eb4b55f1..6effefa5 100644 --- a/pkg/papi/papi_test.go +++ b/pkg/papi/papi_test.go @@ -8,8 +8,8 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/papi/products.go b/pkg/papi/products.go index c58ee041..51cf5397 100644 --- a/pkg/papi/products.go +++ b/pkg/papi/products.go @@ -10,14 +10,6 @@ import ( ) type ( - // Products contains operations available on Products resource - Products interface { - // GetProducts lists all available Products - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-products - GetProducts(context.Context, GetProductsRequest) (*GetProductsResponse, error) - } - // GetProductsRequest contains data required to list products associated to a contract GetProductsRequest struct { ContractID string diff --git a/pkg/papi/property.go b/pkg/papi/property.go index 90fe520c..057cdc20 100644 --- a/pkg/papi/property.go +++ b/pkg/papi/property.go @@ -7,39 +7,11 @@ import ( "net/http" "net/url" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // Properties contains operations available on Property resource - Properties interface { - // GetProperties lists properties available for the current contract and group - // - // https://techdocs.akamai.com/property-mgr/reference/get-properties - GetProperties(ctx context.Context, r GetPropertiesRequest) (*GetPropertiesResponse, error) - - // CreateProperty creates a new property from scratch or bases one on another property's rule tree and optionally its set of assigned hostnames - // - // https://techdocs.akamai.com/property-mgr/reference/post-properties - CreateProperty(ctx context.Context, params CreatePropertyRequest) (*CreatePropertyResponse, error) - - // GetProperty gets a specific property - // - // https://techdocs.akamai.com/property-mgr/reference/get-property - GetProperty(ctx context.Context, params GetPropertyRequest) (*GetPropertyResponse, error) - - // RemoveProperty removes a specific property, which you can only do if none of its versions are currently active - // - // https://techdocs.akamai.com/property-mgr/reference/delete-property - RemoveProperty(ctx context.Context, params RemovePropertyRequest) (*RemovePropertyResponse, error) - - // MapPropertyNameToID returns (PAPI) property ID for given property name - // Mainly to be used to map (IAM) Property ID to (PAPI) Property ID - // To get property name for the mapping, please use iam.MapPropertyIDToName - MapPropertyNameToID(context.Context, MapPropertyNameToIDRequest) (*string, error) - } - // PropertyCloneFrom optionally identifies another property instance to clone when making a POST request to create a new property PropertyCloneFrom struct { CloneFromVersionEtag string `json:"cloneFromVersionEtag,omitempty"` @@ -56,11 +28,9 @@ type ( GroupID string `json:"groupId"` LatestVersion int `json:"latestVersion"` Note string `json:"note"` - ProductID string `json:"productId"` ProductionVersion *int `json:"productionVersion,omitempty"` PropertyID string `json:"propertyId"` PropertyName string `json:"propertyName"` - RuleFormat string `json:"ruleFormat"` StagingVersion *int `json:"stagingVersion,omitempty"` } diff --git a/pkg/papi/property_test.go b/pkg/papi/property_test.go index 844eec0f..a06093e3 100644 --- a/pkg/papi/property_test.go +++ b/pkg/papi/property_test.go @@ -7,8 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/ptr" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -56,10 +55,9 @@ func TestPapiGetProperties(t *testing.T) { ContractID: "ctr_1-1TJZH5", GroupID: "grp_15166", PropertyID: "prp_175780", - ProductID: "prp_175780", PropertyName: "example.com", LatestVersion: 2, - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), ProductionVersion: nil, AssetID: "aid_101", Note: "Notes about example.com", @@ -146,7 +144,6 @@ func TestPapiGetProperty(t *testing.T) { "propertyName": "example.com", "latestVersion": 2, "stagingVersion": 1, - "productId": "prp_175780", "productionVersion": null, "assetId": "aid_101", "note": "Notes about example.com" @@ -162,10 +159,9 @@ func TestPapiGetProperty(t *testing.T) { ContractID: "ctr_1-1TJZH5", GroupID: "grp_15166", PropertyID: "prp_175780", - ProductID: "prp_175780", PropertyName: "example.com", LatestVersion: 2, - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), ProductionVersion: nil, AssetID: "aid_101", Note: "Notes about example.com", @@ -177,10 +173,9 @@ func TestPapiGetProperty(t *testing.T) { ContractID: "ctr_1-1TJZH5", GroupID: "grp_15166", PropertyID: "prp_175780", - ProductID: "prp_175780", PropertyName: "example.com", LatestVersion: 2, - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), ProductionVersion: nil, AssetID: "aid_101", Note: "Notes about example.com", diff --git a/pkg/papi/propertyhostname.go b/pkg/papi/propertyhostname.go index 14813894..9900e43a 100644 --- a/pkg/papi/propertyhostname.go +++ b/pkg/papi/propertyhostname.go @@ -10,19 +10,6 @@ import ( ) type ( - // PropertyVersionHostnames contains operations available on PropertyVersionHostnames resource - PropertyVersionHostnames interface { - // GetPropertyVersionHostnames lists all the hostnames assigned to a property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-hostnames - GetPropertyVersionHostnames(context.Context, GetPropertyVersionHostnamesRequest) (*GetPropertyVersionHostnamesResponse, error) - - // UpdatePropertyVersionHostnames modifies the set of hostnames for a property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/patch-property-version-hostnames - UpdatePropertyVersionHostnames(context.Context, UpdatePropertyVersionHostnamesRequest) (*UpdatePropertyVersionHostnamesResponse, error) - } - // GetPropertyVersionHostnamesRequest contains parameters required to list property version hostnames GetPropertyVersionHostnamesRequest struct { PropertyID string diff --git a/pkg/papi/propertyversion.go b/pkg/papi/propertyversion.go index 8c881fea..af6df251 100644 --- a/pkg/papi/propertyversion.go +++ b/pkg/papi/propertyversion.go @@ -9,54 +9,11 @@ import ( "net/url" "strconv" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // PropertyVersions contains operations available on PropertyVersions resource - PropertyVersions interface { - // GetPropertyVersions fetches available property versions - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-versions - GetPropertyVersions(context.Context, GetPropertyVersionsRequest) (*GetPropertyVersionsResponse, error) - - // GetPropertyVersion fetches specific property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version - GetPropertyVersion(context.Context, GetPropertyVersionRequest) (*GetPropertyVersionsResponse, error) - - // CreatePropertyVersion creates a new property version and returns location and number for the new version - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-property-versions - CreatePropertyVersion(context.Context, CreatePropertyVersionRequest) (*CreatePropertyVersionResponse, error) - - // GetLatestVersion fetches the latest property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-latest-property-version - GetLatestVersion(context.Context, GetLatestVersionRequest) (*GetPropertyVersionsResponse, error) - - // GetAvailableBehaviors fetches a list of available behaviors for given property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-available-behaviors - GetAvailableBehaviors(context.Context, GetAvailableBehaviorsRequest) (*GetBehaviorsResponse, error) - - // GetAvailableCriteria fetches a list of available criteria for given property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-available-criteria - GetAvailableCriteria(context.Context, GetAvailableCriteriaRequest) (*GetCriteriaResponse, error) - - // ListAvailableIncludes lists external resources that can be applied within a property version's rules - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-external-resources - ListAvailableIncludes(context.Context, ListAvailableIncludesRequest) (*ListAvailableIncludesResponse, error) - - // ListReferencedIncludes lists referenced includes for parent property - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-includes - ListReferencedIncludes(context.Context, ListReferencedIncludesRequest) (*ListReferencedIncludesResponse, error) - } - // GetPropertyVersionsRequest contains path and query params used for listing property versions GetPropertyVersionsRequest struct { PropertyID string diff --git a/pkg/papi/propertyversion_test.go b/pkg/papi/propertyversion_test.go index 7f088abb..51e488e2 100644 --- a/pkg/papi/propertyversion_test.go +++ b/pkg/papi/propertyversion_test.go @@ -7,8 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" - + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1851,7 +1850,7 @@ func TestPapiListReferencedIncludes(t *testing.T) { IncludeType: IncludeTypeMicroServices, LatestVersion: 1, ProductionVersion: nil, - StagingVersion: tools.IntPtr(1), + StagingVersion: ptr.To(1), }, }, }, diff --git a/pkg/papi/rule.go b/pkg/papi/rule.go index dd5de22e..c13e1443 100644 --- a/pkg/papi/rule.go +++ b/pkg/papi/rule.go @@ -7,24 +7,11 @@ import ( "net/http" "regexp" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegriderr" validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( - // PropertyRules contains operations available on PropertyRule resource - PropertyRules interface { - // GetRuleTree gets the entire rule tree for a property version. - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-property-version-rules - GetRuleTree(context.Context, GetRuleTreeRequest) (*GetRuleTreeResponse, error) - - // UpdateRuleTree updates the rule tree for a property version - // - // See: https://techdocs.akamai.com/property-mgr/reference/put-property-version-rules - UpdateRuleTree(context.Context, UpdateRulesRequest) (*UpdateRulesResponse, error) - } - // GetRuleTreeRequest contains path and query params necessary to perform GET /rules request GetRuleTreeRequest struct { PropertyID string diff --git a/pkg/papi/rule_test.go b/pkg/papi/rule_test.go index 4075005e..13e3055a 100644 --- a/pkg/papi/rule_test.go +++ b/pkg/papi/rule_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -181,11 +181,11 @@ func TestPapiGetRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -336,11 +336,11 @@ func TestPapiGetRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -488,11 +488,11 @@ func TestPapiGetRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -723,11 +723,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -921,11 +921,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { TemplateLink: "/platformtoolkit/service/ruletemplate/30582260/1?accountId=1-1TJZFB&gid=61726&ck=16.3.1.1", Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1020,15 +1020,15 @@ func TestPapiUpdateRuleTree(t *testing.T) { Variables: []RuleVariable{ { Name: "TEST_EMPTY_FIELDS", - Value: tools.StringPtr(""), - Description: tools.StringPtr(""), + Value: ptr.To(""), + Description: ptr.To(""), Hidden: true, Sensitive: false, }, { Name: "TEST_NIL_DESCRIPTION", Description: nil, - Value: tools.StringPtr(""), + Value: ptr.To(""), Hidden: true, Sensitive: false, }, @@ -1232,15 +1232,15 @@ func TestPapiUpdateRuleTree(t *testing.T) { Variables: []RuleVariable{ { Name: "TEST_EMPTY_FIELDS", - Value: tools.StringPtr(""), - Description: tools.StringPtr(""), + Value: ptr.To(""), + Description: ptr.To(""), Hidden: true, Sensitive: false, }, { Name: "TEST_NIL_FIELDS", Description: nil, - Value: tools.StringPtr(""), + Value: ptr.To(""), Hidden: true, Sensitive: false, }, @@ -1281,8 +1281,8 @@ func TestPapiUpdateRuleTree(t *testing.T) { Variables: []RuleVariable{ { Name: "TEST_EMPTY_FIELDS", - Value: tools.StringPtr(""), - Description: tools.StringPtr(""), + Value: ptr.To(""), + Description: ptr.To(""), Hidden: true, Sensitive: false, }, @@ -1373,11 +1373,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1470,11 +1470,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1553,11 +1553,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1637,11 +1637,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1721,11 +1721,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1805,11 +1805,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1889,11 +1889,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "VAR_NAME", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, @@ -1973,11 +1973,11 @@ func TestPapiUpdateRuleTree(t *testing.T) { }, Variables: []RuleVariable{ { - Description: tools.StringPtr("This is a sample Property Manager variable."), + Description: ptr.To("This is a sample Property Manager variable."), Hidden: false, Name: "", Sensitive: false, - Value: tools.StringPtr("default value"), + Value: ptr.To("default value"), }, }, }, diff --git a/pkg/papi/ruleformats.go b/pkg/papi/ruleformats.go index dc20df06..7fa3518f 100644 --- a/pkg/papi/ruleformats.go +++ b/pkg/papi/ruleformats.go @@ -8,14 +8,6 @@ import ( ) type ( - // RuleFormats contains operations available on RuleFormat resource - RuleFormats interface { - // GetRuleFormats provides a list of rule formats - // - // See: https://techdocs.akamai.com/property-mgr/reference/get-rule-formats - GetRuleFormats(context.Context) (*GetRuleFormatsResponse, error) - } - // GetRuleFormatsResponse contains the response body of GET /rule-formats request GetRuleFormatsResponse struct { RuleFormats RuleFormatItems `json:"ruleFormats"` diff --git a/pkg/papi/search.go b/pkg/papi/search.go index b996f59c..08266b82 100644 --- a/pkg/papi/search.go +++ b/pkg/papi/search.go @@ -10,14 +10,6 @@ import ( ) type ( - // Search contains SearchProperty method used for fetching properties - Search interface { - // SearchProperties searches properties by name, or by the hostname or edge hostname for which it’s currently active - // - // See: https://techdocs.akamai.com/property-mgr/reference/post-search-find-by-value - SearchProperties(context.Context, SearchRequest) (*SearchResponse, error) - } - // SearchResponse contains response body of POST /search request SearchResponse struct { Versions SearchItems `json:"versions"` diff --git a/pkg/session/request_test.go b/pkg/session/request_test.go index 7e32c057..767b8d08 100644 --- a/pkg/session/request_test.go +++ b/pkg/session/request_test.go @@ -9,7 +9,7 @@ import ( "net/url" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/session/session.go b/pkg/session/session.go index 02c1913e..f35920c9 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -7,7 +7,7 @@ import ( "runtime" "strings" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" "github.com/apex/log" "github.com/apex/log/handlers/discard" ) @@ -63,7 +63,7 @@ var ( const ( // Version is the client version - Version = "8.0.0" + Version = "9.0.0" ) // New returns a new session diff --git a/pkg/session/session_test.go b/pkg/session/session_test.go index 1802bee4..ac498e02 100644 --- a/pkg/session/session_test.go +++ b/pkg/session/session_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v9/pkg/edgegrid" "github.com/apex/log" "github.com/apex/log/handlers/discard" "github.com/stretchr/testify/require" @@ -28,7 +28,7 @@ func TestNew(t *testing.T) { signer: &edgegrid.Config{}, log: log.Log, trace: false, - userAgent: "Akamai-Open-Edgegrid-golang/8.0.0 golang/" + strings.TrimPrefix(runtime.Version(), "go"), + userAgent: "Akamai-Open-Edgegrid-golang/9.0.0 golang/" + strings.TrimPrefix(runtime.Version(), "go"), }, }, "with options provided": { diff --git a/pkg/tools/ptr.go b/pkg/tools/ptr.go deleted file mode 100644 index d7cfc0b7..00000000 --- a/pkg/tools/ptr.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package tools contains utilities used in EdgeGrid -package tools - -// BoolPtr returns the address of the bool -// -// Deprecated: this function will be removed in a future release. Use [ptr.To] instead. -func BoolPtr(b bool) *bool { - return &b -} - -// IntPtr returns the address of the int -// -// Deprecated: this function will be removed in a future release. Use [ptr.To] instead. -func IntPtr(i int) *int { - return &i -} - -// Int64Ptr returns the address of the int64 -// -// Deprecated: this function will be removed in a future release. Use [ptr.To] instead. -func Int64Ptr(i int64) *int64 { - return &i -} - -// Float32Ptr returns the address of the float32 -// -// Deprecated: this function will be removed in a future release. Use [ptr.To] instead. -func Float32Ptr(f float32) *float32 { - return &f -} - -// Float64Ptr returns the address of the float64 -// -// Deprecated: this function will be removed in a future release. Use [ptr.To] instead. -func Float64Ptr(f float64) *float64 { - return &f -} - -// StringPtr returns the address of the string -// -// Deprecated: this function will be removed in a future release. Use [ptr.To] instead. -func StringPtr(s string) *string { - return &s -}