-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Check the uniqueness of the project name during pulumi new
#3065
Conversation
pulumi new
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments, but otherwise LGTM. Thanks!
I wasn't able to find any existing tests for the parts that I touched. Let me know if you think we should have some tests here.
Test coverage has been lacking in this area and is something we want to improve. Tests are always welcome, but I understand it may be a larger effort to get some initial test helpers/infrastructure in place to more easily test this command incrementally going forward. So unless you see a way to add some simple tests now, we can just track getting that in place as a separate effort.
@@ -473,6 +473,16 @@ func (b *cloudBackend) Logout() error { | |||
return workspace.DeleteAccessToken(b.CloudURL()) | |||
} | |||
|
|||
// DoesProjectExist returns true if a project with the given name exists in this backend, or false otherwise. | |||
func (b *cloudBackend) DoesProjectExist(ctx context.Context, projectName string) (bool, error) { | |||
owner, err := b.client.GetPulumiAccountName(ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we get to adding the ideal experience for #2421 (prompting for the owner), I think we'll need to modify this to allow passing the owner to DoesProjectExist
since this is always only checking if the project exists in the user's account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Moreover, this is already a problem now:
- We ask for a project name, we get
proj1
and it's unique in the user's account. - We ask for a stack name, we get
org1/stack1
back. - Project
proj1
already exists inorg1
. Boom.
It seems that we are not able to validate the project name before we get the org name, or am I wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that we are not able to validate the project name before we get the org name, or am I wrong?
Right. I think we just live with this gotcha until #3076 is addressed.
cmd/new.go
Outdated
@@ -367,6 +371,29 @@ func errorIfNotEmptyDirectory(path string) error { | |||
return nil | |||
} | |||
|
|||
func validateProjectName(projectName string, opts display.Options) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the pulumi new
command does accept a --name
(or -n
) flag and we validate this early on and exit early if the specified project name isn't valid. But that validation isn't calling the new DoesProjectExist
.
So even with the changes in this PR I could do:
pulumi new -n exists
And pulumi
will happily continue and prompt for the project description and stack name, and will create the stack in the existing project assuming I choose a stack name that doesn't conflict with an existing stack name in the existing project.
I wonder if we should add another check that calls this function to ensure the project doesn't exist earlier on. Perhaps after this block:
Lines 121 to 127 in 1a698cb
// If we're going to be creating a stack, get the current backend, which | |
// will kick off the login flow (if not already logged-in). | |
if !generateOnly { | |
if _, err = currentBackend(opts); err != nil { | |
return err | |
} | |
} |
Something like:
// Ensure the project doesn't already exist.
if !generateOnly && name != "" {
if err := validateProjectName(name, opts); err != nil {
return err
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, when we get to the ideal fix for #2421 (prompting for the owner), we'll probably need to make some modifications to such a check as it'll need an owner to perform the "does exist" check. Perhaps it'd only do the check if it has an owner at this point (e.g. the user is logged in to the cloud backend and isn't a member of any orgs, and hence the user account would be the owner). Or if we decided to add an --owner
flag and the flag was specified.
cmd/new.go
Outdated
return err | ||
} | ||
|
||
exists, err := b.DoesProjectExist(commandContext(), projectName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the --generate-only
(or -g
) flag is specified, we won't be creating a stack. I wonder if in that case we should just skip getting the backend and calling b.DoesProjectExist
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the project name still needs to be unique in this case, doesn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the project name still needs to be unique in this case, doesn't it?
It does, but when -g
is specified, no actual stack is going to be created as part of the pulumi new
command invocation. I suppose it is likely they'll run pulumi stack init
as a next step at some point (or indirectly via running pulumi up
and it prompting to create a stack). And having new
check to ensure the project is unique before any of these subsequent commands could be helpful. However, by the time they run the command that creates the stack, the projects on the server could have changed (although unlikely), or they'll want to create a stack in a different org, in which case the check in the user's org was pointless and would artificially block them when they just want to emit the project on disk.
So I guess I lean slightly towards not doing the check when -g
is specified. It's not creating any stacks, so don't bother with with the uniqueness check. But I could be convinced otherwise.
e585529
to
ca4e16b
Compare
@justinvp I feel that there are enough scenario permutations here to warrant spending time on building a test suite. I created two basic tests - could you have a look and check whether they are on the proper level? I had to mock the console input to be able to provide user's input: otherwise, we'll lose many scenarios. The next step would be to mock the backend to test the name validation. What would be the optimal way to do so? Mock the whole backend like |
These look fine to me, but would love @ellismg to take a look.
@ellismg, what would you recommend? |
42e5e55
to
5a8e037
Compare
I don't think I have any silver bullets here. I think that mocking the backend is correct (we could probably just return not implemented errors for codepaths we did not think would be hit). As far as injecting it goes, I think that passing it to Long term, we probably want the backend injected into any command that needs it, so we could mock more of the interactive portions of the CLI. |
@justinvp @ellismg Thanks a ton for your input! I've restructured the code a bit further to split the actual function from the command which makes testing and injection a bit easier. I've also injected a mock backend with a naughty assignment - I might refactor it to an argument later on. I added a new test which depends on the mocked backend. This looks promising, so I'll go ahead and try to create enough test cases to cover the combinations for the issue in question. |
I added several more tests, I believe this is ready for another review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM otherwise. Thanks for the tests and getting this infrastructure in place, Mikhail!
cmd/new_test.go
Outdated
assert.Equal(t, uniqueProjectName, proj.Name.String()) | ||
} | ||
|
||
func TestCreatingProjectWithEnteredName(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference between SpecifiedName
and EnteredName
? In both of these tests uniqueProjectName
is being specified as a the name
arg.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch! I wasn't careful enough and let it slip in. The Entered
ones shouldn't have name
specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a nit, but can we rename the functions to make the distinction more clear? Maybe something like:
- TestCreatingProjectWithArgsSpecifiedName
- TestCreatingProjectWithPromptedName
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple nits on the test names, but LGTM otherwise.
cmd/new_test.go
Outdated
assert.Equal(t, uniqueProjectName, proj.Name.String()) | ||
} | ||
|
||
func TestCreatingProjectWithEnteredName(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a nit, but can we rename the functions to make the distinction more clear? Maybe something like:
- TestCreatingProjectWithArgsSpecifiedName
- TestCreatingProjectWithPromptedName
270024a
to
580819f
Compare
Fixes #2476.
During
pulumi new
, check if there is an existing project with the name equal to the entered value, and prevent going forward if so.@justinvp I wasn't able to find any existing tests for the parts that I touched. Let me know if you think we should have some tests here.