Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable user profile hce 266 #137

Merged
merged 15 commits into from
Dec 1, 2022
Merged

Conversation

JolisaBrownHashiCorp
Copy link
Contributor

@JolisaBrownHashiCorp JolisaBrownHashiCorp commented Nov 23, 2022

🛠️ Description

This PR includes a profile library to enable users setting organization ID, project ID, and development environment on their client configuration instance. With the current changes, users may set profile values via environment variables. Next steps include introducing support for saving profile values optionally in a profile.json file saved in a location chosen by the user.

🔗 External Links

HCE-266 - 'profile' library to handle setting org ID, project ID, environment

👍 Definition of Done

  • SDK added
  • SDK updated
  • Tests added?
  • Docs updated?

@JolisaBrownHashiCorp JolisaBrownHashiCorp requested a review from a team November 23, 2022 20:13
config/hcp.go Outdated
if c.profile.GetOrganizationID() == "" || c.profile.GetProjectID() == "" {
return fmt.Errorf("organization ID and project ID must be non-empty")
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it best to make organizationID and projectID optional as I'm doing here? Or would it be best to throw an error on client initialization to enforce those fields as a requirement on the config struct? Would it be appropriate as well to allow the environment field to be optional or otherwise assign a default value for the field in the profile if not assigned?

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep these optional. But I would add some validation that ensures that if one of them has a value, the other one also has a value. Since they don't make sense without each other.

config/env.go Outdated

envVarHCPProjectID = "HCP_PROJECT_ID"

envVarHCPEnvironment = "HCP_ENVIRONMENT"
Copy link
Contributor

Choose a reason for hiding this comment

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

After some discussion today, let's remove the HCP environment setting from the profile.

Copy link
Contributor

@bcmdarroch bcmdarroch left a comment

Choose a reason for hiding this comment

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

Excellent work on this! I had a few comments to address, but it's very close.

config/env.go Outdated
@@ -52,6 +58,18 @@ const (
// It will not fail if no or only part of the variables are present.
func FromEnv() HCPConfigOption {
return func(config *hcpConfig) error {
// Read user profile information from the environment, the values will only be
Copy link
Contributor

Choose a reason for hiding this comment

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

I would move this block to the bottom of FromEnv(). The client credentials/authURL checks are more critical to the SDK to function and should fail early, whereas profile variables should always be optional.

config/new.go Outdated
@@ -74,6 +75,7 @@ func NewHCPConfig(opts ...HCPConfigOption) (HCPConfig, error) {
Scopes: []string{"openid", "offline_access"},
},
session: &auth.UserSession{},
profile: profile.UserProfile{},
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make this a pointer, so we're always mutating the same object if the profile needs to change.

Suggested change
profile: profile.UserProfile{},
profile: &profile.UserProfile{},

config/with.go Outdated
// WithProfile is an option that can be used to provide a custom Profile struct.
//
// This should only be necessary for development purposes.
func WithProfile(organizationID, projectID, hcpEnvironment string) HCPConfigOption {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a good opportunity for using the Profile interface as a single parameter, so we don't have to keep updating this function signature every time we change what's in the profile.

hcpEnvironment string
}

//TODO: Do we need to include comments here or is it covered since we comment the function definitions in the interface?
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the comments might be more helpful next to their actual function definition, rather than within the interface.

"os"
)

type Profile interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

One more question, more philosophical so I'll leave it up to you. Right now I'm not sure I see the need for a Profile interface. I think we could get away with just the UserProfile struct with the methods defined below. It's true that we could keep the interface and add a mock implementation, the way we did with Session. But where would we use that mock implementation? Would it provide anything useful? This package just seems straightforward enough to not require much code 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm, I'd think it might be best as well to stick with just using the struct since I'm finding myself maneuvering a bit to find utility for a mock implementation. I agree that since the methods are mostly managing the struct properties, the interface might be a bit overkill. I can go ahead and make that tweak then!

}

func (p *UserProfile) SetOrganizationID(id string) error {
err := os.Setenv(envVarHCPOrganizationID, id)
Copy link
Contributor

Choose a reason for hiding this comment

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

RE: our earlier discussion, we don't want to overwrite environment variables, only read them. Setting the org ID should only involve setting the value in the UserProfile struct.

package profile

const (
envVarHCPOrganizationID = "HCP_ORGANIZATION_ID"
Copy link
Contributor

Choose a reason for hiding this comment

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

These constants are already defined in env.go

config/env.go Outdated
if hcpOrganizationIDOK && hcpProjectIDOK {
userProfile := profile.UserProfile{OrganizationID: hcpOrganizationID, ProjectID: hcpProjectID}
if err := apply(config, WithProfile(&userProfile)); err != nil {
return fmt.Errorf("failed to set profile fields from environment variables (%s, %s): %w", envVarHCPOrganizationID, envVarHCPProjectID, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency with the errors above:

Suggested change
return fmt.Errorf("failed to set profile fields from environment variables (%s, %s): %w", envVarHCPOrganizationID, envVarHCPProjectID, err)
return fmt.Errorf("failed to configure profile based on environment variables (%s, %s): %w", envVarHCPOrganizationID, envVarHCPProjectID, err)

(bonus ask: fix the basd typo I just noticed in those other errors 😅)

@@ -84,6 +85,13 @@ type hcpConfig struct {
// session is responsible for getting an access token fron our identity provider.
// A mock can be used in tests.
session auth.Session

Copy link
Contributor

Choose a reason for hiding this comment

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

a bonus for this PR or a follow-up: let's use a Session pointer

config/hcp.go Outdated
@@ -84,6 +85,13 @@ type hcpConfig struct {
// session is responsible for getting an access token fron our identity provider.
// A mock can be used in tests.
session auth.Session

// profile is user's the org id, project id, and application environment
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// profile is user's the org id, project id, and application environment
// profile is the user's organization ID and project ID

@@ -36,6 +37,8 @@ func TestNew_Options(t *testing.T) {
WithPortalURL("https://my-portal:1234"),
WithAPI("my-api:2345", &tls.Config{}),
WithSCADA("my-scada:3456", &tls.Config{}),
// considering that some fields now are optional...should that mean I should be passing in the Profile struct instance directly here (empty or otherwise)?
Copy link
Contributor

@bcmdarroch bcmdarroch Nov 29, 2022

Choose a reason for hiding this comment

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

I think the purpose of this test is to show all the options that are available to be used, so personally I would populate the UserProfile with a project ID and org ID and then assert that the values are set correctly down below.

config/with.go Outdated

// WithProfile is an option that can be used to provide a custom UserProfile struct.
//
// This should only be necessary for development purposes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// This should only be necessary for development purposes.

// UserProfile implements the Profile interface.
type UserProfile struct {

// hcpOrganizationID is the user's organization ID.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// hcpOrganizationID is the user's organization ID.
// OrganizationID is the user's organization ID.

// hcpOrganizationID is the user's organization ID.
OrganizationID string

// hcpProjectID is the user's project ID.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// hcpProjectID is the user's project ID.
// ProjectID is the user's project ID.

ProjectID string
}

// getOrganizationID() retrieves a user's organization ID.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// getOrganizationID() retrieves a user's organization ID.
// GetOrganizationID() retrieves a user's organization ID.

return p.OrganizationID
}

// getProjectID() retrieves the user's project ID.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// getProjectID() retrieves the user's project ID.
// GetProjectID() retrieves the user's project ID.

envVarHCPProjectID = "HCP_PROJECT_ID"
)

// UserProfile implements the Profile interface.
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't really true anymore, so let's make this comment more descriptive

@JolisaBrownHashiCorp JolisaBrownHashiCorp merged commit e90102f into main Dec 1, 2022
@JolisaBrownHashiCorp JolisaBrownHashiCorp deleted the enable-user-profile-hce-266 branch December 1, 2022 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants