Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

support for feature l2connection PrimaryZSideServiceToken#8

Merged
displague merged 4 commits intomasterfrom
zside_service_token_feature
Jul 15, 2022
Merged

support for feature l2connection PrimaryZSideServiceToken#8
displague merged 4 commits intomasterfrom
zside_service_token_feature

Conversation

@ocobles
Copy link
Copy Markdown
Contributor

@ocobles ocobles commented Jul 13, 2022

Feature required to support z-side service token.

As described in code both a-side/z-side are returned in vendorToken attribute. A little logic has been implemented to distinguish a-side/z-side tokens.

@ocobles ocobles requested review from cprivitere and displague July 13, 2022 14:47
@displague
Copy link
Copy Markdown
Member

This field is documented at https://developer.equinix.com/docs/fabric-connect-using-service-token-parameters-mapping

This PR is in support of equinix/terraform-provider-equinix#223

Comment thread rest_l2_connection.go Outdated
//since service tokens are created at port level and not through an SP, and SPs are always
//the z-side of the connection, so in that case service token can only be used for the a-side.
var serviceToken, zSideServiceToken *string
if getResponse.SellerServiceUUID != nil || getResponse.AuthorizationKey != nil {
Copy link
Copy Markdown
Member

@displague displague Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to create a second service token field in the SDK (diverging from the API spec) when only one field will ever be set?

Should we keep the response object the same (matching the upstream API spec) and include a helper for this detection?

// IsZside determines if a connection is a zSide connection.
// This is especially helpful in determining if the ServiceToken (when present) is a zSide token.
func (l *L2Connection) IsZside() bool {
   return ! (l.SellerServiceUUID != nil || l.AuthorizationKey != nil)
}

(probably want to reverse the logic to avoid the double negatives)

return l.SellerServiceUUID == nil && l.AuthorizationKey == nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, this helper could be left up to the user (TF) to implement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason was that API POST request have them both, while GET just vendorToken, it simplifies the interface, otherwise the L2Connection object will require three attributes

type L2Connection struct {
	...
	ServiceToken        *string
	ZSideServiceToken   *string
	VendorToken         *string
}

However, I agree we don't want this to diverge from API, but ServiceToken was already used in prev releases to return a-side token. Is that a problem?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I see. This is the intermediary type that represents the Terraform interface?
This approach makes sense if the intent is to create two separate fields in Terraform.
I'll throw the question back to you - does that improve the Terraform experience? What does it look like one way or the other?

Copy link
Copy Markdown
Contributor Author

@ocobles ocobles Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was a misunderstanding in my prev answer. API has both fields primaryServiceToken and primaryZsideServiceToken in the POST request, despite the fact that just one of them can be applied (maybe something that will change on the future), and GET doesn't have those fields but vendorToken, so I expect same behavoir in the sdk. Even if we keep a single token field, I would like to hold the information in some way to know if it was a-side or a z-side, It can be relevant for users. Below there are some of the options that come to my mind for the terraform side.

Option 1 - Applying all logic at sdk level:

//Create 
if v, ok := d.GetOk("service_token"); ok {
  primary.ServiceToken = ecx.String(v.(string))
  }
if v, ok := d.GetOk("zside_service_token"]); ok {
  primary.ZSideServiceToken = ecx.String(v.(string))
 }
 
 //Read
if err := d.Set("service_token"], primary.ServiceToken); err != nil {
  return fmt.Errorf(err)
}
if err := d.Set("zside_service_token"], primary.ZSideServiceToken); err != nil {
  return fmt.Errorf(err)
}

Option 2 - Using IsZside and TF just a single "service_token" attr. We don't modify GET output, so serviceToken/zsideServiceToken will be empty, but user (TF) can use IsZside. There's a problem with this approach (not for TF since it will just set nil in d.Set(...) ), it assumes connection was created using a token, but token is just one of the options. I mean, if vendorToken is nil, isZside can still return true/false.
This will also require extra validations in TF create function, since a-side and z-side have different limitations, for example z-side tokens aren't available for redundant/secondary connections, otherwise just leave API returns back an error.

//Create 
if v, ok := d.GetOk("service_token"); ok {
  primary.ServiceToken = ecx.String(v.(string))
}
 
 //Read
if primary.IsZside && err := d.Set("service_token"], primary.ZSideServiceToken); err != nil {
  return fmt.Errorf(err)
} else if err := d.Set("service_token"], primary.ServiceToken); err != nil {
  return fmt.Errorf(err)
}

Option 3 - Same behavior API/SDK/TF - No update service_token/zside_service_token in read func, expose vendorToken (computed) in SDK and in TF the schema:

//Create 
Same as opt. 1
 
 //Read
if  err := d.Set("vendor_token"], primary.VendorToken); err != nil {
  return fmt.Errorf(err)
}

Option 4 -No logic at all, just use the previous value in the schema for service_token/zside_service_token in read func. This option breaks TF import

//Create 
Same as opt. 1
 
 //Read
N/A

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, option1 or option3 are best approach for terraform. Option3 is the way for auto-gen sdks and/or TF modules, matching behavior across API and consumers, option1 looks more like the expected user experience (but in this case will differ from API), so if attribute was "service_token" then taking back value from API It should use same attribute and not another computed argument having same value

Copy link
Copy Markdown
Contributor Author

@ocobles ocobles Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found out that a-side can be used to connect to your own port using e-line ( although I don't know if makes sense), so looking just for service provider fields is no guarantee of a-side. I see tokenCustOrgId in z-side requests but I'm not sure if it will always come. Tthis makes option 3 the most reliable

Comment thread rest_l2_connection.go
RedundancyGroup: getResponse.RedundancyGroup,
Actions: mapL2ConnectionActionsAPIToDomain(getResponse.ActionDetails),
ServiceToken: getResponse.VendorToken,
VendorToken: getResponse.VendorToken,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be sure to call this out as a breaking change, "L2Connection.ServiceToken has been renamed to VendorToken" (or we can deprecate the old field and set the response value to both field names).

Copy link
Copy Markdown
Member

@displague displague Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, I see the field is still present, but it no longer receives a value. Is that the intent? Should we deprecate the field? Is there a response value that should be copied here?

https://github.com/equinix/ecx-go/pull/8/files?diff=unified&w=1#diff-4b667feae66c9d46b21b9ecc19e8958cf4472d162ce0a47ac3e8386af8bbd8cfR103

Copy link
Copy Markdown
Contributor Author

@ocobles ocobles Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L2Connection is shared across the different interfaces of the client, ServiceToken/ZSide ServiceToken must be there for the POST

ecx-go/client.go

Lines 44 to 45 in 52abbf7

GetL2Connection(uuid string) (*L2Connection, error)
CreateL2Connection(conn L2Connection) (*string, error)

I can keep ServiceToken = getResponse.VendorToken with a deprecation note but not sure what is the right way for that more than what I can set in the changelog

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this:

// ServiceToken something something
// Starting with v2.3.0, this field will not be populated with …, see VendorToken for that. The previous behavior was to …
ServiceToken *string

@displague displague merged commit d178d37 into master Jul 15, 2022
@displague displague deleted the zside_service_token_feature branch July 15, 2022 14:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants