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

feat(cmd): import and export namespace flag #1452

Merged
merged 18 commits into from
Mar 31, 2023

Conversation

GeorgeMac
Copy link
Contributor

Fixes FLI-258

This PR primarily adds namespace support to both import and export:

flipt import --namespace foo --create-namespace
flipt export --namespace foo

Along the way I moved a lot of stuff around to support it.

Firstly, I set about refactoring the cmd package. Primarily, I moved a lot of global stuff into functions.
This helped me get to a spot where --config is no longer required for import or export (it still is for main and migrate).

I wanted this to be optional for these commands. Such that they can now support import and exporting state into and out of Flipt via the API and using the SDK.

flipt import --import-to-address (http(s)|grpc)://localhost:8080 --import-to-token <client-token>
flipt import --export-from-address (http(s)|grpc)://localhost:8080 --export-from-token <client-token>

This meant updating some of the core import and export code to work over a higher abstraction.
They were (export at least) working directly on the storage APIs.
Now they operate through the grpc APIs and the SDK fits in nicely.

The major motivation here was to adds integrations tests for import and export. Which this PR includes.
I ended up doing a lot of overhaul in the test suite code to fit these in.

However, now that its done, the import and export integration suite runs in the same matrix as the API suite.
This new test suite does the following:

  1. flipt import into a target instance with some seed data.
  2. Uses the SDK to do some read-only API calls to check that state is reachable.
  3. flipt export the data and compares with the original seed data.

As mentioned, this suite is ran in every combination the API tests are ran in.
Meaning we run it protocol x namespace x authenticated times (8).

The integartion suite now runs 16 different configurations.

@GeorgeMac GeorgeMac requested a review from a team as a code owner March 30, 2023 17:23
@codecov
Copy link

codecov bot commented Mar 30, 2023

Codecov Report

Merging #1452 (76e4867) into namespaces (d9500dc) will decrease coverage by 0.37%.
The diff coverage is 75.40%.

@@              Coverage Diff               @@
##           namespaces    #1452      +/-   ##
==============================================
- Coverage       78.19%   77.82%   -0.37%     
==============================================
  Files              44       44              
  Lines            3348     3405      +57     
==============================================
+ Hits             2618     2650      +32     
- Misses            578      602      +24     
- Partials          152      153       +1     
Impacted Files Coverage Δ
internal/storage/sql/migrator.go 20.00% <0.00%> (-2.79%) ⬇️
internal/ext/importer.go 70.06% <74.54%> (-5.50%) ⬇️
internal/ext/exporter.go 88.72% <100.00%> (+2.11%) ⬆️
internal/server/evaluator.go 94.73% <100.00%> (+0.04%) ⬆️
internal/storage/sql/db.go 91.36% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Collaborator

@markphelps markphelps left a comment

Choose a reason for hiding this comment

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

still working through but wanted to post some initial thoughts. looking great btw!

cmd/flipt/export.go Outdated Show resolved Hide resolved
cmd/flipt/export.go Outdated Show resolved Hide resolved
cmd/flipt/export.go Outdated Show resolved Hide resolved
cmd/flipt/import.go Outdated Show resolved Hide resolved
}

banner = buf.String()

cobra.OnInitialize(func() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

i remember there being some weird reason we had to do this previously, it had something to do with maybe the logger not being initialized properly before trying to use it? I'm guessing the changes to move the logger into the cmds themselves above addresses this?

Copy link
Contributor Author

@GeorgeMac GeorgeMac Mar 31, 2023

Choose a reason for hiding this comment

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

I popped cobra to have a look; initializers run just before Run or RunE, but after flag parsing.

There are some bits of validating that go on between invoking the run function and after calling this function.
However, I can't necessarily see that causing problems, since we don't seem to use half the things which get triggered (e.g. PreRun and so on), let alone have a dependency order which is dictated by these:
https://github.com/spf13/cobra/blob/v1.6.1/command.go#L876-L921

Running the same process at the start of Run / RunE should be equivalent in our situation.
Allowing us to not dictate that every function has to parse a config.yaml in a particular location in order to be invoked. That was my motivation at-least, since OnInitialize will run in all circumstances.

I was going to just make the default of --config be empty string and switch on that. But then realized that would actually be a breaking change if folks are relying on just editing the default yaml file at the usual location and ommitting the flag.

"destination namespace for imported resources.",
)

cmd.Flags().BoolVar(
Copy link
Collaborator

Choose a reason for hiding this comment

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

flipt import --namespace foo --create-namespace seems a bit repetitive to me. I also wonder what the user would expect here by default, would they expect that importing to a non-existing namespace creates the namespace without having to specify additional options? similar to how importing flags that don't exist just creates the flags?

Alternatively, I wonder if in the future we might have other types of resources that could not exist but we'd want to create if a given CLI flag was present. In that case, maybe a more generic --force / -f or similar would be appropriate? just wonder how 'special' we want to make namespaces overall

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My motivation here was to match helm (helm/helm#7648). It is repetetive but explicit and does one thing.
Whereas -f to me suggests that perhaps validation is bypassed, not extra resources may be created if not exists.

I can't imagine what other resources we may need to create in this way in the future.
This command inherently creates all the resources in the file you're importing anyway.

I would probably lean on --create-namespace still myself. However, I am open to more ideas.

We could move namespace as a resource inside the manifests themselves and expect the caller to include it.
This is what kubernetes expects. It would make it more explicit and remove the need for a flag.
However, that comes with added complexity in API use and implementation.
K8s actually fails if you try to apply a bunch of resources and the namespace is in the same payload.
You have to do them one at a time to ensure the namespace is created before its contents.

Equally, we could forgo this entirely. Expect it to exists and I can update the tests to work around that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah that explanation makes sense to me, especially since there is prior art in the k8s/helm space. thanks!

Copy link
Contributor

@yquansah yquansah left a comment

Choose a reason for hiding this comment

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

LGTM, just a couple questions

go func() {
<-interrupt
cancel()
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be racy at all? I am just wondering in comparison to waiting on the receive from interrupt at the bottom of the function.

Now the interrupt signal can be received whenever this goroutine is initiated. But I guess in the end it's just canceling the context.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am wondering if it's worth the additional memory overhead of spinning up another goroutine here as well.

Copy link
Contributor Author

@GeorgeMac GeorgeMac Mar 31, 2023

Choose a reason for hiding this comment

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

I wouldn't worry about a single goroutine. There about 4k of memory if I remember correctly (may have changed).
Concerns r.e. memory and goroutines is more when you're creating them in an unbound loop, and worse when there is no way to tear them down. This case is once per execution and exits when the signal gets trapped and closes the interrupt channel.

We could move from signal.Notify (which is what we have always called) to signal.NotifyContext (relatively new in the grand scheme of Go stdlib).
Though, that said, signal.NotifyContext is the same thing. It is just a goroutine that cancels a context when the signal channel sends:
https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/os/signal/signal.go;l=277-296

Copy link
Contributor Author

@GeorgeMac GeorgeMac Mar 31, 2023

Choose a reason for hiding this comment

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

Last comments on your question r.e. is it racey?

Here the only things we're sharing access to are interrupt and cancel.
The first is a channel that we're receiving on. We're performing a synchronisation operation which is inherently not racey.

The cancel function is also safe to handle concurrently. From the docs:

A CancelFunc may be called by multiple goroutines simultaneously. After the first call, subsequent calls to a CancelFunc do nothing.

Copy link
Contributor

Choose a reason for hiding this comment

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

@GeorgeMac Such a great explanantion. I appreciate this George 👍.

Copy link
Collaborator

Choose a reason for hiding this comment

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

does exiting with error (fatal) / non-user initiated cause the interrupt signal to be sent over the interrupt channel?

previously if run exited we would always cancel the context since cancel() was in a defer call, with moving to this change I'm just wondering if there are any side effects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cancel() is still called in a defer. Just in main and not inside the cobra run command:
https://github.com/flipt-io/flipt/pull/1452/files/af7e193555d908c366f091622bf6697259492bab#diff-f0fc8b9cf7ff242b701bc3eece5ca189b334ecf1382930bc34c4ae3b4d312e55R118

Prior to this change we were doing signal handling inside the cobra run commands, as opposed to just once at the top in main for all commands.

When it comes to fatal though, it always results in an immediate call to os.Exit and nothing defered gets run anyway.
With panics you get defer function calls. However, in this situation probably best to just finish asap with non zero exit code.

cmd/flipt/export.go Outdated Show resolved Hide resolved
Copy link
Contributor

@yquansah yquansah left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Collaborator

@markphelps markphelps left a comment

Choose a reason for hiding this comment

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

Looks GREAT to me!

Just one minor question 🙌🏻

@GeorgeMac GeorgeMac merged commit 641d69d into namespaces Mar 31, 2023
@GeorgeMac GeorgeMac deleted the gm/namespaces-export-import branch March 31, 2023 15:15
markphelps added a commit that referenced this pull request Apr 5, 2023
* feat: (sqlite) support namespace at storage level, mostly for flags (#1368)

* feat: (sqlite) wip support namespace at storage level, mostly for flags

* chore: rename down namespace file correctly

* chore: fix server/flag_test.go

* chore: redo migration to use temp_tables for composite keys

* chore: just use TODO for now for down migration

* chore: comments

* chore: wip namespace protos

* chore: better err messages

* Update internal/storage/sql/common/flag.go

Co-authored-by: George <me@georgemac.com>

* chore: fix tests

* chore: split into seperate migrations

* chore: more error messages

* chore: rm println statment

---------

Co-authored-by: George <me@georgemac.com>

* feat: Namespaces segments storage (#1369)

* feat: (sqlite) add namespace support for rules storage (#1371)

* feat: (sqlite) eval storage namespace support (#1372)

* Namespaces storage (#1383)

* feat: wip namespace storage

* chore: add tests; storage impl

* chore: appease the linter

* chore: less sleepy

* Namespaces mysql (#1386)

* feat: wip namespace storage

* chore: add tests; storage impl

* chore: appease the linter

* feat: (wip) add namespace mysql migrations

* chore: rename migrations

* chore: rm empty migrations for the moment

* chore: less sleepy

* chore: 6 bytes for timestamp

* chore: add rules migration

* chore: add migration comments, revert local config

* chore: reset mysql example

* Namespaces postgres (#1388)

* feat: (wip) postgres ddl

* chore: fix tests

* chore: disable container logging unless verbose env var passed

* Namespaces cockroach (#1390)

* feat: cockroachdb migrations

* chore: fix migrations for cockroach

* chore: namespaces down migrations (#1396)

* Namespaces: flags storage tests (#1406)

* Namespaces: segments storage tests (#1408)

* chore: add segment namespace storage tests

* chore: regen protos

* chore: ignore sdk dir when fmting

* chore: add remaining storage layer tests (#1412)

* feat: add namespaces server mappings (#1415)

* chore: add remaining storage layer tests

* feat: add namespaces server mappings

* chore: add back default namespace to otel metrics

* chore: fix middleware test

* Namespaces rpc (#1421)

* feat: check for protected or flags existing when deleting a namespace (#1422)

* feat: add namespaces server impl

* chore: spacing

* chore: rm ability to set protected

* feat: check for protected or flags existing when deleting a namespace

* chore: fix test

* chore: add test for non-existing delete

* feat(hack/build): add cases for namespace scoped integration tests (#1436)

* chore: regenerate protobuf

* feat(rpc/flipt): configure gateway routes for namespaces

* feat(hack/build): add cases for namespace scoped integration tests

* chore(rpc/flipt): remove TODO

* chore(hack/build): use random string for namespace key

* test(hack/build): ensure default namespace cannot be deleted

* test(hack/build): ensure default namespace is protected but updateable

* test(hack/build): ensure namespace with flags cannot be deleted

* feat: add check for rule namespaced errors

* fix: namespace segment REST API routes were not correct (#1445)

* feat: add distribution test for cross-namespace entities

* feat: leave namespace check to distribution

* feat: generate namespaced scoped routes for distributions

* chore: address changes in regards to error messaging

* chore: abstract out the two types of DistributionRequests

* fix(migrations/sqlite): reorder to ensure we copy all data before drop (#1446)

* fix(migrations/sqlite): reorder to ensure we copy all data before drop

* fix(migrations/sqlite): copy distributions into temp table before dropping everything

* chore: do joins on the query and make error messages match

* chore: merge main into namespaces (#1448)

* chore(deps): bump go.opentelemetry.io/otel/exporters/zipkin (#1440)

Bumps [go.opentelemetry.io/otel/exporters/zipkin](https://github.com/open-telemetry/opentelemetry-go) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](open-telemetry/opentelemetry-go@v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/zipkin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(github): dagger based integrations tests github workflow (#1428)

* feat(github): install dagger based ITs as action workflow

* fix(github): add workdir to mage action

* chore(hack/build): bump dagger to 0.5.1

* chore: enable daggers experimental gha cache

* chore(hack/build): reword test log lines

* fix(github): move env var from legacy to experimental tests

* chore(hack/build): arbitrary change to trigger rebuild

* chore(github): remove experimental dagger cache env var

* feat(hack/build): direct integration test flipt logs into directory

* chore: empty commit to trigger CI

* chore(hack/build): log error when failing to copy flipt logs

* chore(hack/build): adding log line to integration test start

* fix(github): stop caching entire hack build directory

* refactor(hack/build): organize integration cases into test case struct

* refactor(github): remove legacy API workflows and replace with dagger mage task

* chore(hack/build): use protocolPorts map for test case iteration

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: add missing namespace metrics (#1450)

* chore: allow testing of namespaces ui branch for build

* chore: rm down migrations (#1449)

* chore: wip rm down migrations

* chore: fix tests

* chore: fix tests; cleanup

* chore: use new migrator on import if drop specified

* chore: fix drop before import for sqlite

* chore: move mysql fk disable into migrator down method

* chore: codecov's last chance

* feat(cmd): import and export namespace flag (#1452)

* refactor(export): promote namespace to NewExporter argument

* refactor(export): use GRPC service interface as exporter dependency

* feat(export): support --export-from-address remote flipt address flag

* refactor(cmd): share config as argument not global state

* refactor(cmd/export): move command boostrapping from main.go to export.go

* refactor(cmd): move signal trapping into main

* refactor(cmd/import): move command boostrapping from main.go to import.go

* refactor(ext): expose namespace as argument to NewImporter

* feat(cmd/import): support remote import via --import-to-address

* refactor(hack/build): restructure testing packages

* refactor(hack/build): create import and export integration tests

* feat(hack/build): more import integration test cases

* feat(cmd/export): support --namespace flag

* chore: go mod tidy

* fix(cmd): do not close db early in import/export

* refactor(cmd): rename import/export flags to --address and --token

* refactor(cmd/import): move server config into server.go

* fix(cmd/import): pass namespace key as name on create

* fix(hack/build): assert returned namespace name is as expected

* chore: validation logic for namespace requests (#1457)

* chore: validation logic for namespace requests

* chore: fix import command to use the namespace key as the name as well

* chore: rm TODO comments in exporter

* chore: mage proto

* chore: update changelog for future namespace release; add mage proto lint check (#1460)

* chore: trigger lint build

* chore: rm mage proto check for now as its not working

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: George <me@georgemac.com>
Co-authored-by: Yoofi Quansah <ybquansah@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

3 participants