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

CLOUDP-289219: Create CLI commands for manipulating stream processors #3496

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

auddin431
Copy link
Collaborator

Proposed changes

This work adds the noun processors to the top level noun streams so that stream processors can be interacted with through the CLI. The processors noun has the following verbs:

  • create
  • delete
  • update
  • list
  • describe
  • start
  • stop

Jira ticket: CLOUDP-289219

Checklist

  • I have signed the MongoDB CLA
  • I have added tests that prove my fix is effective or that my feature works
  • I have added any necessary documentation in document requirements section listed in CONTRIBUTING.md (if appropriate)
  • I have addressed the @mongodb/docs-cloud-team comments (if appropriate)
  • I have updated test/README.md (if an e2e test has been added)
  • I have run make fmt and formatted my code

@auddin431 auddin431 requested review from a team as code owners December 19, 2024 22:06
Copy link
Collaborator

@tcannon91 tcannon91 left a comment

Choose a reason for hiding this comment

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

Next time, it might be a little easier to review if we break down the commits into each command or each command into their own PR :)

Dropping comments because I think I have gone through all of the create command. I haven't looked but I will try not to leave the same comments on the other commands if they have the same issues. I will continue looking at the other commands and try to only leave net new stuff

Comment on lines +71 to +91
ListProcessors(*atlasv2.ListStreamProcessorsApiParams) (*atlasv2.PaginatedApiStreamsStreamProcessorWithStats, error)
}

type ProcessorDescriber interface {
StreamProcessor(*atlasv2.GetStreamProcessorApiParams) (*atlasv2.StreamsProcessorWithStats, error)
}

type ProcessorStarter interface {
StartStreamProcessor(*atlasv2.StartStreamProcessorApiParams) error
}

type ProcessorStopper interface {
StopStreamProcessor(*atlasv2.StopStreamProcessorApiParams) error
}

type ProcessorDeleter interface {
DeleteStreamProcessor(string, string, string) error
}

type ProcessorCreator interface {
CreateStreamProcessor(*atlasv2.CreateStreamProcessorApiParams) (*atlasv2.StreamsProcessor, error)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you name the arguments to each of these functions?

For example, it is hard to know what each of the parameters are/should be for DeleteStreamProcessor

}

// DeleteStreamProcessor deletes the specified Stream Processor.
func (s *Store) DeleteStreamProcessor(projectID, tenantName, processorName string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is interesting that the other functions here all take a request and pull what they need while this method takes each value as a string. I have a personal preference towards this approach (explicitly calling for what you need rather than taking a whole request object), but more important than one direction vs the other I think we should be consistent.

Pick one approach and stick to it for the implementations unless there is something that makes this method especially unique.

Comment on lines +68 to +71
* - -o, --output
- string
- false
- Output format. Valid values are json, json-path, go-template, or go-template-file. To see the full output, use the -o json option.
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the purpose of outputing a create command? Why would someone want to dump Processor <Name> created. to a file?

},
}

cmd.Flags().StringVar(&opts.ProjectID, flag.ProjectID, "", usage.ProjectID)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we start using:

Suggested change
cmd.Flags().StringVar(&opts.ProjectID, flag.ProjectID, "", usage.ProjectID)
opts.AddProjectOptsFlags(cmd)

Comment on lines +116 to +119
_ = cmd.MarkFlagFilename(flag.File)

_ = cmd.MarkFlagRequired(flag.Instance)
_ = cmd.MarkFlagRequired(flag.File)
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nit] Is it a linting rule that makes us have to assign to something? I think it would be a little cleaner to do something like this:

Suggested change
_ = cmd.MarkFlagFilename(flag.File)
_ = cmd.MarkFlagRequired(flag.Instance)
_ = cmd.MarkFlagRequired(flag.File)
cmd.MarkFlagFilename(flag.File)
cmd.MarkFlagRequired(flag.Instance)
cmd.MarkFlagRequired(flag.File)

* - -f, --file
- string
- true
- Path to a JSON configuration file that defines an Atlas Stream Processing connection.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this creating stream processor connection or a stream processor?

Comment on lines +21 to +22
require.Error(t, err)
assert.Contains(t, err.Error(), "missing file")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could these be combined?

Suggested change
require.Error(t, err)
assert.Contains(t, err.Error(), "missing file")
assert.Contains(t, err, "missing file")

Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar comment for all the error assertions

},
}

name := "ExampleSP"
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nit] we should either inline this down where it is used or use string interpolation to inject this value into the string that is written to the config file so that it is forced to be aligned with the test input and the expected output. Assigning it to a variable that we don't reuse feels weird.

if err := createOpts.Run(); err != nil {
t.Fatalf("Run() unexpected error: %v", err)
}
t.Log(buf.String())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this a debugging line? Maybe it should be removed?

t.Fatalf("Run() unexpected error: %v", err)
}
t.Log(buf.String())
test.VerifyOutputTemplate(t, createTemplate, expected)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does this assertion verify? It isn't clear what it actually does, we might want a comment here, especially because I think it might be asserting the same thing as the next line?

Copy link
Collaborator

@tcannon91 tcannon91 left a comment

Choose a reason for hiding this comment

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

E2E tests reviewed

Copy link
Collaborator

Choose a reason for hiding this comment

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

[q] I am only seeing this used by changes from 8 months ago. Any ideas why it is being added in your PR/where it is coming from?

a.Equal(processorName, *processor.Name)
})

t.Run("Describe a stream processor with an atlas cluster sink", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

All of the tests below this one concern me because they are relying on data populated/created by previous t.Run() tests. Each test should be able to function in isolation. If you want to rely on previous configuration, then it should be nested within the test.

I would recommend each of the commands describe, list, delete, start, stop do all of the creation/setup necessary and just don't run assertions on those steps (since the assertions are covered here).

An alternative idea is to create a larger E2E test that tests everything. It could create, do assertions on the creation; describe, do assertions on the describe; list and assert; start and assert; stop and assert; and delete and assert.

Copy link
Collaborator

@tcannon91 tcannon91 left a comment

Choose a reason for hiding this comment

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

Delete and Describe reviewed

}
cmd := &cobra.Command{
Use: "delete <processorName>",
Short: "Delete a specific Atlas Stream Processor in a Stream Processing Instance.",
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nit]

Suggested change
Short: "Delete a specific Atlas Stream Processor in a Stream Processing Instance.",
Short: "Delete an Atlas Stream Processor in a Stream Processing Instance.",

cmd := &cobra.Command{
Use: "delete <processorName>",
Short: "Delete a specific Atlas Stream Processor in a Stream Processing Instance.",
Long: fmt.Sprintf(usage.RequiredRole, "Project Read Only"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This role doesn't seem correct.... I don't think you should be able to delete with a read only role.

"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks"
)

func TestDeleteOpts_Run(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There should be a t.Run() call in this test describing what is being tested/asserted.

opts := &DescribeOpts{}
cmd := &cobra.Command{
Use: "describe <processorName>",
Short: "Get details about a specific Atlas Stream Processor in a Stream Processing Instance.",
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nit]

Suggested change
Short: "Get details about a specific Atlas Stream Processor in a Stream Processing Instance.",
Short: "Get details about an Atlas Stream Processor in a Stream Processing Instance.",

func DescribeBuilder() *cobra.Command {
opts := &DescribeOpts{}
cmd := &cobra.Command{
Use: "describe <processorName>",
Copy link
Collaborator

Choose a reason for hiding this comment

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

fwiw, this command seems like it would make more sense to have a json output file that is the .json configuration file that would work with the create command (and eventually update).

Comment on lines +40 to +45
if opts.includeStats {
return opts.Print(r)
}

sp := atlasv2.NewStreamsProcessorWithStats(r.Id, r.Name, r.Pipeline, r.State)
return opts.Print(sp)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the logic is backwards here... we should be returning early if !opts.includeStats

return opts.Print(r)
}

sp := atlasv2.NewStreamsProcessorWithStats(r.Id, r.Name, r.Pipeline, r.State)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be created through a store method. I am not familiar with the decision patterns, I have only seen the CLI interact with the api through stores rather than directly like this.

Comment on lines +47 to +51
mockStore.
EXPECT().
StreamProcessor(gomock.Any()).
Return(expected, nil).
Times(2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that separate stores should be pulled into each test to more specifically assert each test's code.

Copy link
Collaborator

@tcannon91 tcannon91 left a comment

Choose a reason for hiding this comment

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

Start and Stop reviewed

cmd := &cobra.Command{
Use: "start <processorName>",
Short: "Start a specific Atlas Stream Processor in a Stream Processing Instance.",
Long: fmt.Sprintf(usage.RequiredRole, "Project Read Only"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

The docs indicate this command will require either Project Owner or Project Stream Processing Owner


mockStore.
EXPECT().
StartStreamProcessor(gomock.Any()).
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should have stricter assertions on what we pass to the store calls. Splitting the commands out into separate strings might make this easier to assert on then reconstructing Params objects. But it shouldn't be too hard either way.

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