diff --git a/go.mod b/go.mod index 2f1de60d88..49c951b477 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 github.com/hashicorp/terraform-plugin-testing v1.5.1 - github.com/manicminer/hamilton v0.68.0 + github.com/manicminer/hamilton v0.70.0 golang.org/x/text v0.14.0 ) diff --git a/go.sum b/go.sum index 23761cc43b..c6eace182f 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/manicminer/hamilton v0.68.0 h1:2ZKQRTegktxJIoLBWxI43HczpTiwXl6brvwTXy75gPk= -github.com/manicminer/hamilton v0.68.0/go.mod h1:u80g9rPtJpCG7EC0iayttt8UfeAp6jknClixgZGE950= +github.com/manicminer/hamilton v0.70.0 h1:XMgVcwVtUGq2aBXqVAynaUDZdPXCXC8/rd7D5Zsg3UM= +github.com/manicminer/hamilton v0.70.0/go.mod h1:u80g9rPtJpCG7EC0iayttt8UfeAp6jknClixgZGE950= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= diff --git a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go index e4981be4d6..e8627e7ca7 100644 --- a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go +++ b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go @@ -19,13 +19,27 @@ import ( type RoleEligibilityScheduleRequestResource struct{} -func TestAccRoleEligibilityScheduleRequest_basic(t *testing.T) { +func TestAccRoleEligibilityScheduleRequest_builtin(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_directory_role_eligibility_schedule_request", "test") r := RoleEligibilityScheduleRequestResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.basic(data), + Config: r.builtin(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + }) +} + +func TestAccRoleEligibilityScheduleRequest_custom(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_directory_role_eligibility_schedule_request", "test") + r := RoleEligibilityScheduleRequestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.custom(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -48,7 +62,7 @@ func (r RoleEligibilityScheduleRequestResource) Exists(ctx context.Context, clie return pointer.To(resr.ID != nil && *resr.ID == state.ID), nil } -func (r RoleEligibilityScheduleRequestResource) basic(data acceptance.TestData) string { +func (r RoleEligibilityScheduleRequestResource) builtin(data acceptance.TestData) string { return fmt.Sprintf(` provider "azuread" {} @@ -74,3 +88,36 @@ resource "azuread_directory_role_eligibility_schedule_request" "test" { } `, data.RandomInteger, data.RandomPassword) } + +func (r RoleEligibilityScheduleRequestResource) custom(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "test" { + user_principal_name = "acctestManager.%[1]d@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestManager-%[1]d" + password = "%[2]s" +} + +resource "azuread_custom_directory_role" "test" { + display_name = "acctestCustomRole-%[1]d" + enabled = true + version = "1.0" + + permissions { + allowed_resource_actions = ["microsoft.directory/applications/standard/read"] + } +} + +resource "azuread_directory_role_eligibility_schedule_request" "test" { + role_definition_id = azuread_custom_directory_role.test.object_id + principal_id = azuread_user.test.object_id + directory_scope_id = "/" + justification = "abc" +} +`, data.RandomInteger, data.RandomPassword) +} diff --git a/internal/services/identitygovernance/access_package_assignment_policy_resource_test.go b/internal/services/identitygovernance/access_package_assignment_policy_resource_test.go index 8e1ea7c69a..0215150035 100644 --- a/internal/services/identitygovernance/access_package_assignment_policy_resource_test.go +++ b/internal/services/identitygovernance/access_package_assignment_policy_resource_test.go @@ -76,6 +76,13 @@ func TestAccAccessPackageAssignmentPolicy_update(t *testing.T) { ), }, data.ImportStep(), + { + Config: r.simple(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), { Config: r.complete(data), Check: acceptance.ComposeTestCheckFunc( diff --git a/internal/services/identitygovernance/identitygovernance.go b/internal/services/identitygovernance/identitygovernance.go index bd77c36c0b..8142de5a6c 100644 --- a/internal/services/identitygovernance/identitygovernance.go +++ b/internal/services/identitygovernance/identitygovernance.go @@ -131,6 +131,17 @@ func expandAssignmentReviewSettings(input []interface{}) (*msgraph.AssignmentRev result.Reviewers = expandUserSets(in["reviewer"].([]interface{})) + if result.AccessReviewTimeoutBehavior == "" && + (result.DurationInDays == nil || *result.DurationInDays == 0) && + (result.IsAccessRecommendationEnabled == nil || !*result.IsAccessRecommendationEnabled) && + (result.IsApprovalJustificationRequired == nil || !*result.IsApprovalJustificationRequired) && + (result.IsEnabled == nil || !*result.IsEnabled) && + result.RecurrenceType == "" && + result.ReviewerType == "" && + (result.Reviewers == nil || len(*result.Reviewers) == 0) { + return nil, nil + } + return &result, nil } diff --git a/vendor/github.com/manicminer/hamilton/msgraph/application_templates.go b/vendor/github.com/manicminer/hamilton/msgraph/application_templates.go index ca24bf8c57..1aeac9db4b 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/application_templates.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/application_templates.go @@ -95,8 +95,9 @@ func (c *ApplicationTemplatesClient) Instantiate(ctx context.Context, applicatio } resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ - Body: body, - ValidStatusCodes: []int{http.StatusCreated}, + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusCreated}, Uri: Uri{ Entity: fmt.Sprintf("/applicationTemplates/%s/instantiate", *applicationTemplate.ID), }, diff --git a/vendor/github.com/manicminer/hamilton/msgraph/attribute_set.go b/vendor/github.com/manicminer/hamilton/msgraph/attribute_set.go new file mode 100644 index 0000000000..05ded44645 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/attribute_set.go @@ -0,0 +1,165 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +const ( + attributeSetEntity = "/directory/attributeSets" +) + +type AttributeSetClient struct { + BaseClient Client +} + +func NewAttributeSetClient() *AttributeSetClient { + return &AttributeSetClient{ + BaseClient: NewClient(Version10), + } +} + +func (c *AttributeSetClient) List(ctx context.Context, query odata.Query) (*[]AttributeSet, int, error) { + resp, status, _, err := c.BaseClient.Get( + ctx, + GetHttpRequestInput{ + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: attributeSetEntity, + }, + }, + ) + if err != nil { + return nil, status, fmt.Errorf("AttributeSet.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + AttributeSets []AttributeSet `json:"value"` + } + + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.AttributeSets, status, nil +} + +func (c *AttributeSetClient) Create(ctx context.Context, attributeSet AttributeSet) (*AttributeSet, int, error) { + var status int + var newAttributeSet AttributeSet + + body, err := json.Marshal(attributeSet) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + requestInput := PostHttpRequestInput{ + Body: body, + OData: odata.Query{ + Metadata: odata.MetadataFull, + }, + ValidStatusCodes: []int{ + http.StatusCreated, + http.StatusOK, + }, + Uri: Uri{ + Entity: attributeSetEntity, + }, + } + + resp, status, _, err := c.BaseClient.Post(ctx, requestInput) + if err != nil { + return nil, status, fmt.Errorf("AttributeSetClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + if err := json.Unmarshal(respBody, &newAttributeSet); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal():%v", err) + } + + return &newAttributeSet, status, nil +} + +func (c *AttributeSetClient) Get(ctx context.Context, id string, query odata.Query) (*AttributeSet, int, error) { + var AttributeSet AttributeSet + + resp, status, _, err := c.BaseClient.Get( + ctx, + GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", attributeSetEntity, id), + }, + }, + ) + if err != nil { + return nil, status, fmt.Errorf("AttributeSetClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + if err := json.Unmarshal(respBody, &AttributeSet); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &AttributeSet, status, nil +} + +func (c *AttributeSetClient) Update(ctx context.Context, AttributeSet AttributeSet) (int, error) { + var status int + + if AttributeSet.ID == nil { + return status, fmt.Errorf("cannot update AttributeSet with a nil ID") + } + + id := *AttributeSet.ID + AttributeSet.ID = nil + + body, err := json.Marshal(AttributeSet) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + + _, status, _, err = c.BaseClient.Patch( + ctx, + PatchHttpRequestInput{ + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{ + http.StatusOK, + http.StatusNoContent, + }, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", attributeSetEntity, id), + }, + }, + ) + if err != nil { + return status, fmt.Errorf("AttributeSetClient.BaseClient.Patch(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/custom_security_attributes.go b/vendor/github.com/manicminer/hamilton/msgraph/custom_security_attributes.go new file mode 100644 index 0000000000..d3337e783e --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/custom_security_attributes.go @@ -0,0 +1,226 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" + "github.com/manicminer/hamilton/internal/utils" +) + +const ( + // customSecurityAttributeDefinitionEntity is a static string used by all methods on the + // CustomSecurityAttributeDefinitionClient struct + customSecurityAttributeDefinitionEntity = "/directory/customSecurityAttributeDefinitions" +) + +// CustomSecurityAttributeDefinitionClient returns a BaseClient to enable interaction with the +// graph API +type CustomSecurityAttributeDefinitionClient struct { + BaseClient Client +} + +// NewCustomSecurityAttributeDefinitionClient returns a new instance of +// CustomSecurityAttributeDefinitionClient +func NewCustomSecurityAttributeDefinitionClient() *CustomSecurityAttributeDefinitionClient { + return &CustomSecurityAttributeDefinitionClient{ + BaseClient: NewClient(Version10), + } +} + +// List returns a slice of CustomSecurityAttributeDefinition, the HTTP status code and any errors +func (c *CustomSecurityAttributeDefinitionClient) List(ctx context.Context, query odata.Query) (*[]CustomSecurityAttributeDefinition, int, error) { + resp, status, _, err := c.BaseClient.Get( + ctx, + GetHttpRequestInput{ + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: customSecurityAttributeDefinitionEntity, + }, + }, + ) + if err != nil { + return nil, status, fmt.Errorf("CustomSecurityAttributeDefinition.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + CustomSecurityAttributeDefinitions []CustomSecurityAttributeDefinition `json:"value"` + } + + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.CustomSecurityAttributeDefinitions, status, nil +} + +// Create will create a CustomSecurityAttributeDefinition and return the result, HTTP status code +// as well as any errors +func (c *CustomSecurityAttributeDefinitionClient) Create(ctx context.Context, customSecurityAttributeDefinition CustomSecurityAttributeDefinition) (*CustomSecurityAttributeDefinition, int, error) { + var status int + var newCustomSecurityAttributeDefinition CustomSecurityAttributeDefinition + + body, err := json.Marshal(customSecurityAttributeDefinition) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + requestInput := PostHttpRequestInput{ + Body: body, + OData: odata.Query{ + Metadata: odata.MetadataFull, + }, + ValidStatusCodes: []int{http.StatusCreated}, + Uri: Uri{ + Entity: customSecurityAttributeDefinitionEntity, + }, + } + + resp, status, _, err := c.BaseClient.Post(ctx, requestInput) + if err != nil { + return nil, status, fmt.Errorf("CustomSecurityAttributeDefinitionClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + if err := json.Unmarshal(respBody, &newCustomSecurityAttributeDefinition); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal():%v", err) + } + + return &newCustomSecurityAttributeDefinition, status, nil +} + +// Get returns a single CustomSecurityAttributeDefinition, HTTP status code, and any errors +func (c *CustomSecurityAttributeDefinitionClient) Get(ctx context.Context, id string, query odata.Query) (*CustomSecurityAttributeDefinition, int, error) { + var customSecurityAttributeDefinition CustomSecurityAttributeDefinition + + resp, status, _, err := c.BaseClient.Get( + ctx, + GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", customSecurityAttributeDefinitionEntity, id), + }, + }, + ) + if err != nil { + return nil, status, fmt.Errorf("CustomSecurityAttributeDefinitionClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + if err := json.Unmarshal(respBody, &customSecurityAttributeDefinition); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &customSecurityAttributeDefinition, status, nil +} + +// Update will update a single CustomSecurityAttributeDefinition entity returning the HTTP status +// code and any errors +func (c *CustomSecurityAttributeDefinitionClient) Update(ctx context.Context, customSecurityAttributeDefinition CustomSecurityAttributeDefinition) (int, error) { + var status int + + if customSecurityAttributeDefinition.ID == nil { + return status, fmt.Errorf("cannot update customSecurityAttributeDefinition with a nil ID") + } + + id := *customSecurityAttributeDefinition.ID + customSecurityAttributeDefinition.ID = nil + + body, err := json.Marshal(customSecurityAttributeDefinition) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + + _, status, _, err = c.BaseClient.Patch( + ctx, + PatchHttpRequestInput{ + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{ + http.StatusOK, + http.StatusNoContent, + }, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", customSecurityAttributeDefinitionEntity, id), + }, + }, + ) + if err != nil { + return status, fmt.Errorf("CustomSecurityAttributeDefinitionClient.BaseClient.Patch(): %v", err) + } + + return status, nil +} + +// Delete removes an instance of CustomSecurityAttributeDefinition by `id` +func (c *CustomSecurityAttributeDefinitionClient) Delete(ctx context.Context, id string) (int, error) { + _, status, _, err := c.BaseClient.Delete( + ctx, + DeleteHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", customSecurityAttributeDefinitionEntity, id), + }, + }, + ) + if err != nil { + return status, fmt.Errorf("CustomSecurityAttributeDefinitionClient.BaseClient.Delete(): %v", err) + } + + return status, nil +} + +func (c *CustomSecurityAttributeDefinitionClient) Deactivate(ctx context.Context, id string) (int, error) { + var status int + var customSecurityAttributeDefinition CustomSecurityAttributeDefinition + + customSecurityAttributeDefinition.Status = utils.StringPtr("Deprecated") + + body, err := json.Marshal(customSecurityAttributeDefinition) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + + _, status, _, err = c.BaseClient.Patch( + ctx, + PatchHttpRequestInput{ + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{ + http.StatusOK, + http.StatusNoContent, + }, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", customSecurityAttributeDefinitionEntity, id), + }, + }, + ) + if err != nil { + return status, fmt.Errorf("customSecurityAttributeDefinitionClient.BaseClient.Patch(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/models.go b/vendor/github.com/manicminer/hamilton/msgraph/models.go index 66d89bf9e4..d542facbca 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/models.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/models.go @@ -64,7 +64,7 @@ type AccessPackageAssignmentRequest struct { type AccessPackageAssignmentPolicy struct { AccessPackageId *string `json:"accessPackageId,omitempty"` - AccessReviewSettings *AssignmentReviewSettings `json:"accessReviewSettings,omitempty"` + AccessReviewSettings *AssignmentReviewSettings `json:"accessReviewSettings"` CanExtend *bool `json:"canExtend,omitempty"` CreatedBy *string `json:"createdBy,omitempty"` CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` @@ -2242,3 +2242,21 @@ type UserFlowAttribute struct { UserFlowAttributeType *string `json:"userFlowAttributeType,omitempty"` DataType *UserflowAttributeDataType `json:"dataType,omitempty"` } + +type AttributeSet struct { + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + MaxAttributesPerSet *int32 `json:"maxAttributesPerSet,omitempty"` +} + +type CustomSecurityAttributeDefinition struct { + AttributeSet *string `json:"attributeSet,omitempty"` + Description *string `json:"description,omitempty"` + ID *string `json:"id,omitempty"` + IsCollection *bool `json:"isCollection,omitempty"` + IsSearchable *bool `json:"isSearchable,omitempty"` + Name *string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` + Type *string `json:"type,omitempty"` + UsePreDefinedValuesOnly *bool `json:"usePreDefinedValuesOnly,omitempty"` +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/users.go b/vendor/github.com/manicminer/hamilton/msgraph/users.go index a68db85137..e6cdf53b4e 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/users.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/users.go @@ -424,3 +424,20 @@ func (c *UsersClient) DeleteManager(ctx context.Context, id string) (int, error) return status, nil } + +// UploadThumbnailPhoto uploads a thumbnail photo for the specified user which should be a gif, jpeg or png image. +func (c *UsersClient) UploadThumbnailPhoto(ctx context.Context, userId, contentType string, thumbnailData []byte) (int, error) { + _, status, _, err := c.BaseClient.Put(ctx, PutHttpRequestInput{ + Body: thumbnailData, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ContentType: contentType, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/users/%s/photo/$value", userId), + }, + }) + if err != nil { + return status, fmt.Errorf("UsersClient.BaseClient.Put(): %v", err) + } + return status, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index fb108cc0f6..00d5ddebea 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -209,7 +209,7 @@ github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.1.1 ## explicit; go 1.15 github.com/hashicorp/yamux -# github.com/manicminer/hamilton v0.68.0 +# github.com/manicminer/hamilton v0.70.0 ## explicit; go 1.21 github.com/manicminer/hamilton/errors github.com/manicminer/hamilton/internal/utils