diff --git a/.hooks/install-hooks.go b/.hooks/install-hooks.go index 11b16a1..886448b 100644 --- a/.hooks/install-hooks.go +++ b/.hooks/install-hooks.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/.hooks/run-hooks.go b/.hooks/run-hooks.go index 1f2edfc..15a4ed5 100644 --- a/.hooks/run-hooks.go +++ b/.hooks/run-hooks.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e5ab2f..ba1413e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Each source has it's own dedicated command. + ### Changed +- `ttnv3` source is named `tts` now. + ### Deprecated +- `--source` flag is now depreciated. + ### Removed ### Fixed diff --git a/README.md b/README.md index 674f470..db887b1 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Binaries are available on [GitHub](https://github.com/TheThingsNetwork/lorawan-s ## Support -- [X] The Things Network Stack V2 -- [X] [ChirpStack Network Server](https://www.chirpstack.io/) -- [X] [The Things Stack](https://www.github.com/TheThingsNetwork/lorawan-stack/) +- [x] The Things Network Stack V2 +- [x] [ChirpStack Network Server](https://www.chirpstack.io/) +- [x] [The Things Stack](https://www.github.com/TheThingsNetwork/lorawan-stack/) - [ ] [Firefly](https://fireflyiot.com/) - [ ] [LORIOT Network Server](https://www.loriot.io/) @@ -71,9 +71,9 @@ To export a single device using its Device ID (e.g. `mydevice`): ```bash # dry run first, verify that no errors occur -$ ttn-lw-migrate device --source ttnv2 "mydevice" --dry-run --verbose > devices.json +$ ttn-lw-migrate ttnv2 device 'mydevice' --dry-run --verbose > devices.json # export device -$ ttn-lw-migrate device --source ttnv2 "mydevice" > devices.json +$ ttn-lw-migrate ttnv2 device 'mydevice' > devices.json ``` In order to export a large number of devices, create a file named `device_ids.txt` with one device ID per line: @@ -90,9 +90,9 @@ And then export with: ```bash # dry run first, verify that no errors occur -$ ttn-lw-migrate devices --source ttnv2 "mydevice" --dry-run --verbose < device_ids.txt > devices.json +$ ttn-lw-migrate ttnv2 devices 'mydevice' --dry-run --verbose < device_ids.txt > devices.json # export devices -$ ttn-lw-migrate devices --source ttnv2 < device_ids.txt > devices.json +$ ttn-lw-migrate ttnv2 devices < device_ids.txt > devices.json ``` ### Export Applications @@ -101,9 +101,9 @@ Similarly, to export all devices of application `my-app-id`: ```bash # dry run first, verify that no errors occur -$ ttn-lw-migrate application --source ttnv2 "my-app-id" --dry-run --verbose > devices.json +$ ttn-lw-migrate ttnv2 application 'my-app-id' --dry-run --verbose > devices.json # export devices -$ ttn-lw-migrate application --source ttnv2 "my-app-id" > devices.json +$ ttn-lw-migrate ttnv2 application 'my-app-id' > devices.json ``` ## ChirpStack @@ -121,7 +121,7 @@ $ export FREQUENCY_PLAN_ID="EU_863_870" # Frequency Plan for exported de See [Frequency Plans](https://thethingsstack.io/reference/frequency-plans/) for the list of frequency plans available on The Things Stack. For example, to use `United States 902-928 MHz, FSB 1`, you need to specify the `US_902_928_FSB_1` frequency plan ID. -> *NOTE*: `JoinEUI` and `FrequencyPlanID` are required because ChirpStack does not store these fields. +> _NOTE_: `JoinEUI` and `FrequencyPlanID` are required because ChirpStack does not store these fields. ### Notes @@ -134,7 +134,7 @@ See [Frequency Plans](https://thethingsstack.io/reference/frequency-plans/) for To export a single device using its DevEUI (e.g. `0102030405060708`): ``` -$ ttn-lw-migrate device --source chirpstack "0102030405060708" > devices.json +$ ttn-lw-migrate chirpstack device '0102030405060708' > devices.json ``` In order to export a large number of devices, create a file named `device_euis.txt` with one DevEUI per line: @@ -151,7 +151,7 @@ In order to export a large number of devices, create a file named `device_euis.t And then export with: ```bash -$ ttn-lw-migrate device --source chirpstack < device_euis.txt > devices.json +$ ttn-lw-migrate chirpstack device < device_euis.txt > devices.json ``` ### Export Applications @@ -159,7 +159,7 @@ $ ttn-lw-migrate device --source chirpstack < device_euis.txt > devices.json Similarly, to export all devices of application `chirpstack-app-1`: ```bash -$ ttn-lw-migrate application --source chirpstack "chirpstack-app-1" > devices.json +$ ttn-lw-migrate chirpstack application 'chirpstack-app-1' > devices.json ``` In order to export multiple applications, create a file named `application_names.txt` with one Application name per line: @@ -173,7 +173,7 @@ chirpstack-app-3 And export with: ```bash -$ ttn-lw-migrate application --source chirpstack < application_names.txt > devices.json +$ ttn-lw-migrate chirpstack application < application_names.txt > devices.json ``` ## The Things Stack @@ -183,13 +183,13 @@ $ ttn-lw-migrate application --source chirpstack < application_names.txt > devic Configure with environment variables, or command-line arguments. See `--help` for more details: ```bash -$ export TTNV3_APP_ID="my-tts-app" # TTS App ID -$ export TTNV3_APP_API_KEY="NNSXS.U..." # TTS App API Key (needs `device` permissions) -$ export TTNV3_APPLICATION_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Application Server URL Address -$ export TTNV3_IDENTITY_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Identity Server URL Address -$ export TTNV3_JOIN_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Join Server URL Address -$ export TTNV3_NETWORK_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Network Server URL Address -$ export TTNV3_CA_FILE="/path/to/ca.file" # Path to a CA file (optional) +$ export TTS_APP_ID="my-tts-app" # TTS App ID +$ export TTS_APP_API_KEY="NNSXS.U..." # TTS App API Key (needs `device` permissions) +$ export TTS_APPLICATION_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Application Server URL Address +$ export TTS_IDENTITY_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Identity Server URL Address +$ export TTS_JOIN_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Join Server URL Address +$ export TTS_NETWORK_SERVER_GRPC_ADDRESS="eu1.cloud.thethings.network:8884" # TTS Network Server URL Address +$ export TTS_CA_FILE="/path/to/ca.file" # Path to a CA file (optional) ``` ### Notes @@ -203,9 +203,9 @@ To export a single device using its Device ID (e.g. `mydevice`): ```bash # dry run first, verify that no errors occur -$ ttn-lw-migrate device --source ttnv3 "mydevice" --dry-run --verbose > devices.json +$ ttn-lw-migrate tts device 'mydevice' --dry-run --verbose > devices.json # export device -$ ttn-lw-migrate device --source ttnv3 "mydevice" > devices.json +$ ttn-lw-migrate tts device 'mydevice' > devices.json ``` In order to export a large number of devices, create a file named `device_ids.txt` with one device ID per line: @@ -222,9 +222,9 @@ And then export with: ```bash # dry run first, verify that no errors occur -$ ttn-lw-migrate devices --source ttnv3 "mydevice" --dry-run --verbose < device_ids.txt > devices.json +$ ttn-lw-migrate tts devices 'mydevice' --dry-run --verbose < device_ids.txt > devices.json # export devices -$ ttn-lw-migrate devices --source ttnv3 < device_ids.txt > devices.json +$ ttn-lw-migrate tts devices < device_ids.txt > devices.json ``` ### Export Applications @@ -233,9 +233,9 @@ Similarly, to export all devices of application `my-app-id`: ```bash # dry run first, verify that no errors occur -$ ttn-lw-migrate application --source ttnv3 "my-app-id" --dry-run --verbose > devices.json +$ ttn-lw-migrate tts application 'my-app-id' --dry-run --verbose > devices.json # export devices -$ ttn-lw-migrate application --source ttnv3 "my-app-id" > devices.json +$ ttn-lw-migrate tts application 'my-app-id' > devices.json ``` ## Development Environment @@ -293,16 +293,21 @@ This will compile binaries for all supported platforms, `deb`, `rpm`, Snapcraft ### Release from master 1. Create a `release/${version}` branch off the `master` branch. + ```bash $ git checkout master $ git checkout -b release/${version} ``` + 2. Update the `CHANGELOG.md` file as explained below: + - Change the **Unreleased** section to the new version and add date obtained via `date +%Y-%m-%d` (e.g. `## [1.0.0] - 2020-10-18`) + - Check if we didn't forget anything important - Remove empty subsections - Update the list of links in the bottom of the file - Add new **Unreleased** section: + ```md ## [Unreleased] @@ -318,12 +323,15 @@ $ git checkout -b release/${version} ### Security ``` + 4. Create a pull request targeting `master`. 5. Once this PR is approved and merged, checkout the latest `master` branch locally. 6. Create a version tag, and push to GitHub: + ```bash $ git tag -s -a "v${version}" -m "ttn-lw-migrate v${version}" $ git push origin "v${version}" ``` + 7. CI will automatically start building and pushing to package managers. When this is done, you'll find a new release on the [releases page](https://github.com/TheThingsNetwork/lorawan-stack-migrate/releases). 8. Edit the release notes on the GitHub releases page, typically copied from `CHANGELOG.md`. diff --git a/cmd/application.go b/cmd/application.go index e3db56e..9580faf 100644 --- a/cmd/application.go +++ b/cmd/application.go @@ -15,22 +15,27 @@ package cmd import ( + "fmt" + "strings" + "github.com/spf13/cobra" + "go.thethings.network/lorawan-stack-migrate/pkg/commands" "go.thethings.network/lorawan-stack-migrate/pkg/source" ) var applicationsCmd = &cobra.Command{ - Use: "application [app-id] ...", - Aliases: []string{"applications", "app"}, - Short: "Export all devices of an application", + Use: "application [app-id] ...", + Short: "Export all devices of an application", + Aliases: []string{"applications", "app"}, + Deprecated: fmt.Sprintf("use [%s] commands instead", strings.Join(source.Names(), "|")), RunE: func(cmd *cobra.Command, args []string) error { - return exportCommand(cmd, args, func(s source.Source, item string) error { - return s.RangeDevices(item, exportCfg.exportDev) + return commands.Export(cmd, args, func(s source.Source, item string) error { + return s.RangeDevices(item, exportCfg.ExportDev) }) }, } func init() { - applicationsCmd.Flags().AddFlagSet(source.FlagSet()) + applicationsCmd.Flags().AddFlagSet(source.AllFlagSets()) rootCmd.AddCommand(applicationsCmd) } diff --git a/cmd/chirpstack/chirpstack.go b/cmd/chirpstack/chirpstack.go new file mode 100644 index 0000000..5d038d8 --- /dev/null +++ b/cmd/chirpstack/chirpstack.go @@ -0,0 +1,22 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package chirpstack + +import "go.thethings.network/lorawan-stack-migrate/pkg/commands" + +const sourceName = "chirpstack" + +// ChirpStackCmd represents the chirpstack source. +var ChirpStackCmd = commands.Source(sourceName, "Export devices from ChirpStack V3") diff --git a/cmd/devices.go b/cmd/devices.go index c48e980..91c1404 100644 --- a/cmd/devices.go +++ b/cmd/devices.go @@ -15,20 +15,25 @@ package cmd import ( + "fmt" + "strings" + "github.com/spf13/cobra" + "go.thethings.network/lorawan-stack-migrate/pkg/commands" "go.thethings.network/lorawan-stack-migrate/pkg/source" ) var devicesCmd = &cobra.Command{ - Use: "device [dev-id] ...", - Short: "Export devices by DevEUI", - Aliases: []string{"end-devices", "end-device", "devices", "dev"}, + Use: "device [dev-id] ...", + Short: "Export devices by DevEUI", + Aliases: []string{"end-devices", "end-device", "devices", "dev"}, + Deprecated: fmt.Sprintf("use [%s] commands instead", strings.Join(source.Names(), "|")), RunE: func(cmd *cobra.Command, args []string) error { - return exportCommand(cmd, args, exportCfg.exportDev) + return commands.Export(cmd, args, exportCfg.ExportDev) }, } func init() { - devicesCmd.Flags().AddFlagSet(source.FlagSet()) + devicesCmd.Flags().AddFlagSet(source.AllFlagSets()) rootCmd.AddCommand(devicesCmd) } diff --git a/cmd/root.go b/cmd/root.go index b5e65c7..841aae5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -19,6 +19,10 @@ import ( "os" "github.com/spf13/cobra" + "go.thethings.network/lorawan-stack-migrate/cmd/chirpstack" + "go.thethings.network/lorawan-stack-migrate/cmd/ttnv2" + "go.thethings.network/lorawan-stack-migrate/cmd/tts" + "go.thethings.network/lorawan-stack-migrate/pkg/export" "go.thethings.network/lorawan-stack-migrate/pkg/source" "go.thethings.network/lorawan-stack/v3/pkg/log" "go.thethings.network/lorawan-stack/v3/pkg/rpcmiddleware/rpclog" @@ -26,9 +30,9 @@ import ( var ( logger *log.Logger - ctx context.Context + ctx = context.Background() + exportCfg = export.Config{} rootCfg = &source.RootConfig - exportCfg = exportConfig{} rootCmd = &cobra.Command{ Use: "ttn-lw-migrate", Short: "Migrate from other LoRaWAN network servers to The Things Stack", @@ -47,12 +51,14 @@ var ( logHandler, log.WithLevel(logLevel), ) - ctx = log.NewContext(context.Background(), logger) + rpclog.ReplaceGrpcLogger(logger) + ctx = log.NewContext(ctx, logger) - exportCfg.devIDPrefix, _ = cmd.Flags().GetString("dev-id-prefix") - exportCfg.euiForID, _ = cmd.Flags().GetBool("set-eui-as-id") + exportCfg.DevIDPrefix, _ = cmd.Flags().GetString("dev-id-prefix") + exportCfg.EUIForID, _ = cmd.Flags().GetBool("set-eui-as-id") + ctx = export.NewContext(ctx, exportCfg) - rpclog.ReplaceGrpcLogger(logger) + cmd.SetContext(ctx) return nil }, } @@ -80,4 +86,11 @@ func init() { "frequency-plans-url", "https://raw.githubusercontent.com/TheThingsNetwork/lorawan-frequency-plans/master", "URL for fetching frequency plans") + + // TODO: After dependency update (https://github.com/TheThingsNetwork/lorawan-stack-migrate/issues/72) + // Create "sources" group in `rootCmd` + + rootCmd.AddCommand(ttnv2.TTNv2Cmd) + rootCmd.AddCommand(tts.TTSCmd) + rootCmd.AddCommand(chirpstack.ChirpStackCmd) } diff --git a/cmd/sources.go b/cmd/sources.go index 2ba4e86..7bdd0da 100644 --- a/cmd/sources.go +++ b/cmd/sources.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/cmd/ttn-lw-migrate/main.go b/cmd/ttn-lw-migrate/main.go index b55f7c3..fc5e7d2 100644 --- a/cmd/ttn-lw-migrate/main.go +++ b/cmd/ttn-lw-migrate/main.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import ( _ "go.thethings.network/lorawan-stack-migrate/pkg/source/chirpstack" // ChirpStack source _ "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv2" // TTNv2 source - _ "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv3" // TTS source + _ "go.thethings.network/lorawan-stack-migrate/pkg/source/tts" // TTS source "go.thethings.network/lorawan-stack-migrate/cmd" ) diff --git a/cmd/ttnv2/ttnv2.go b/cmd/ttnv2/ttnv2.go new file mode 100644 index 0000000..85d4887 --- /dev/null +++ b/cmd/ttnv2/ttnv2.go @@ -0,0 +1,22 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ttnv2 + +import "go.thethings.network/lorawan-stack-migrate/pkg/commands" + +const sourceName = "ttnv2" + +// TTNv2Cmd represents the ttnv2 source. +var TTNv2Cmd = commands.Source(sourceName, "Export devices from TTN V2") diff --git a/cmd/tts/tts.go b/cmd/tts/tts.go new file mode 100644 index 0000000..9b892a0 --- /dev/null +++ b/cmd/tts/tts.go @@ -0,0 +1,25 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tts + +import "go.thethings.network/lorawan-stack-migrate/pkg/commands" + +const sourceName = "tts" + +// TTSCmd represents the tts source. +var TTSCmd = commands.Source(sourceName, + "Export devices from The Things Stack", + commands.WithAliases([]string{"ttnv3"}), +) diff --git a/cmd/util.go b/cmd/util.go index e8ea6db..d332cf8 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,131 +15,13 @@ package cmd import ( - "bufio" "fmt" "io" "os" - "strings" - "go.thethings.network/lorawan-stack-migrate/pkg/source" "go.thethings.network/lorawan-stack/v3/pkg/errors" - "go.thethings.network/lorawan-stack/v3/pkg/jsonpb" - "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" ) -func toJSON(dev *ttnpb.EndDevice) ([]byte, error) { - return jsonpb.TTN().Marshal(dev) -} - -const ( - maxIDLength = 36 -) - -var ( - errExport = errors.Define("export", "export device `{device_id}`") - errFormat = errors.DefineCorruption("format", "format device `{device_id}`") - errInvalidFields = errors.DefineInvalidArgument("invalid_fields", "invalid fields for device `{device_id}`") - errDevIDExceedsMaxLength = errors.Define("dev_id_exceeds_max_length", "device ID `{id}` exceeds max length") - errAppIDExceedsMaxLength = errors.Define("app_id_exceeds_max_length", "application ID `{id}` exceeds max length") - - sanitizeID = strings.NewReplacer("_", "-") -) - -type exportConfig struct { - euiForID bool - devIDPrefix string -} - -func (cfg exportConfig) exportDev(s source.Source, devID string) error { - dev, err := s.ExportDevice(devID) - if err != nil { - return errExport.WithAttributes("device_id", devID).WithCause(err) - } - oldID := dev.Ids.DeviceId - - if eui := dev.Ids.DevEui; cfg.euiForID && eui != nil { - dev.Ids.DeviceId = strings.ToLower(string(eui)) - } - if cfg.devIDPrefix != "" { - dev.Ids.DeviceId = fmt.Sprintf("%s-%s", cfg.devIDPrefix, dev.Ids.DeviceId) - } - - dev.Ids.DeviceId = sanitizeID.Replace(dev.Ids.DeviceId) - if id := dev.Ids.DeviceId; len(id) > maxIDLength { - return errDevIDExceedsMaxLength.WithAttributes("id", id) - } - - if dev.Ids.DeviceId != oldID { - if dev.Attributes == nil { - dev.Attributes = make(map[string]string) - } - dev.Attributes["old-id"] = oldID - } - - dev.Ids.ApplicationIds.ApplicationId = sanitizeID.Replace(dev.Ids.ApplicationIds.ApplicationId) - if id := dev.Ids.ApplicationIds.ApplicationId; len(id) > maxIDLength { - return errAppIDExceedsMaxLength.WithAttributes("id", id) - } - - if err := dev.ValidateFields(); err != nil { - return errInvalidFields.WithAttributes( - "device_id", dev.Ids.DeviceId, - "dev_eui", dev.Ids.DevEui, - ).WithCause(err) - } - b, err := toJSON(dev) - if err != nil { - return errFormat.WithAttributes( - "device_id", dev.Ids.DeviceId, - "dev_eui", dev.Ids.DevEui, - ).WithCause(err) - } - _, err = fmt.Fprintln(os.Stdout, string(b)) - return err -} - -// Iterator returns items -type Iterator interface { - // Next returns the next item from the iterator. io.EOF is returned when no more items are left. - Next() (item string, err error) -} - -type listIterator struct { - items []string - index int -} - -type readerIterator struct { - rd *bufio.Reader - sep byte -} - -// NewListIterator returns a new iterator from a list of items. -func NewListIterator(items []string) Iterator { - return &listIterator{items: items} -} - -func (l *listIterator) Next() (string, error) { - if l.index < len(l.items) { - l.index++ - return l.items[l.index-1], nil - } - return "", io.EOF -} - -// NewReaderIterator returns a new iterator from a reader. -func NewReaderIterator(rd io.Reader, sep byte) Iterator { - return &readerIterator{rd: bufio.NewReader(rd), sep: sep} -} - -func (r *readerIterator) Next() (string, error) { - s, err := r.rd.ReadString(r.sep) - if err == io.EOF && s != "" { - return s, nil - } - return strings.TrimSpace(s), err -} - // printStack prints the error stack to w. func printStack(w io.Writer, err error) { for i, err := range errors.Stack(err) { diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go new file mode 100644 index 0000000..ff34756 --- /dev/null +++ b/pkg/commands/commands.go @@ -0,0 +1,114 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +type CobraRun func(cmd *cobra.Command, args []string) + +type CobraRunE func(cmd *cobra.Command, args []string) error + +// Option allows extending the command when it is instantiated with New. +type Option func(*cobra.Command) + +// New returns a new command. +func New(opts ...Option) *cobra.Command { + cmd := new(cobra.Command) + for _, opt := range opts { + opt(cmd) + } + return cmd +} + +// WithUse returns an option that sets the command's Use field. +func WithUse(s string) Option { + return func(c *cobra.Command) { c.Use = s } +} + +// WithShort returns an option that sets the command's Short field. +func WithShort(s string) Option { + return func(c *cobra.Command) { c.Short = s } +} + +// WithAliases returns an option that sets the command's Aliases field. +func WithAliases(a []string) Option { + return func(c *cobra.Command) { c.Aliases = a } +} + +// TODO: After dependency update (https://github.com/TheThingsNetwork/lorawan-stack-migrate/issues/72) +// Add `WithGroup` option. + +// WithPersistentPreRun returns an option that sets the command's PersistentPreRun field. +func WithPersistentPreRun(f CobraRun) Option { + return func(c *cobra.Command) { c.PersistentPreRun = f } +} + +// WithPersistentPreRunE returns an option that sets the command's PersistentPreRunE field. +func WithPersistentPreRunE(f CobraRunE) Option { + return func(c *cobra.Command) { c.PersistentPreRunE = f } +} + +// WithPreRun returns an option that sets the command's PreRun field. +func WithPreRun(f CobraRun) Option { + return func(c *cobra.Command) { c.PreRun = f } +} + +// WithPreRunE returns an option that sets the command's PreRunE field. +func WithPreRunE(f CobraRunE) Option { + return func(c *cobra.Command) { c.PreRunE = f } +} + +// WithRun returns an option that sets the command's Run field. +func WithRun(f CobraRun) Option { + return func(c *cobra.Command) { c.Run = f } +} + +// WithRunE returns an option that sets the command's RunE field. +func WithRunE(f CobraRunE) Option { + return func(c *cobra.Command) { c.RunE = f } +} + +// WithPostRun returns an option that sets the command's PostRun field. +func WithPostRun(f CobraRun) Option { + return func(c *cobra.Command) { c.PostRun = f } +} + +// WithPostRunE returns an option that sets the command's PostRunE field. +func WithPostRunE(f CobraRunE) Option { + return func(c *cobra.Command) { c.PostRunE = f } +} + +// WithPersistentPostRun returns an option that sets the command's PersistentPostRun field. +func WithPersistentPostRun(f CobraRun) Option { + return func(c *cobra.Command) { c.PersistentPostRun = f } +} + +// WithPersistentPostRunE returns an option that sets the command's PersistentPostRunE field. +func WithPersistentPostRunE(f CobraRunE) Option { + return func(c *cobra.Command) { c.PersistentPostRunE = f } +} + +// WithSubcommands returns an option that adds commands cmd to the command. +func WithSubcommands(cmd ...*cobra.Command) Option { + return func(c *cobra.Command) { c.AddCommand(cmd...) } +} + +// WithFlagSet returns an option that adds a flag set to the command's flags. +func WithFlagSet(fs *pflag.FlagSet) Option { + return func(c *cobra.Command) { c.Flags().AddFlagSet(fs) } +} diff --git a/cmd/export.go b/pkg/commands/export.go similarity index 61% rename from cmd/export.go rename to pkg/commands/export.go index a960844..94fc7f3 100644 --- a/cmd/export.go +++ b/pkg/commands/export.go @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package commands import ( "io" "os" "github.com/spf13/cobra" + "go.thethings.network/lorawan-stack-migrate/pkg/export" "go.thethings.network/lorawan-stack-migrate/pkg/source" + "go.thethings.network/lorawan-stack/v3/pkg/log" ) -func exportCommand(cmd *cobra.Command, args []string, f func(s source.Source, item string) error) error { +func Export(cmd *cobra.Command, args []string, f func(s source.Source, item string) error) error { var iter Iterator switch len(args) { case 0: @@ -31,13 +33,13 @@ func exportCommand(cmd *cobra.Command, args []string, f func(s source.Source, it iter = NewListIterator(args) } - s, err := source.NewSource(ctx) + s, err := source.NewSource(cmd.Context()) if err != nil { return err } defer func() { if err := s.Close(); err != nil { - logger.WithError(err).Fatal("Failed to clean up") + log.FromContext(cmd.Context()).WithError(err).Fatal("Failed to clean up") } }() @@ -59,3 +61,17 @@ func exportCommand(cmd *cobra.Command, args []string, f func(s source.Source, it } } } + +func ExportApplication() CobraRun { + return func(cmd *cobra.Command, args []string) { + Export(cmd, args, func(s source.Source, item string) error { + return s.RangeDevices(item, export.FromContext(cmd.Context()).ExportDev) + }) + } +} + +func ExportDevices() CobraRunE { + return func(cmd *cobra.Command, args []string) error { + return Export(cmd, args, export.FromContext(cmd.Context()).ExportDev) + } +} diff --git a/pkg/commands/iterator.go b/pkg/commands/iterator.go new file mode 100644 index 0000000..e5658b8 --- /dev/null +++ b/pkg/commands/iterator.go @@ -0,0 +1,63 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "bufio" + "io" + "strings" +) + +// Iterator returns items +type Iterator interface { + // Next returns the next item from the iterator. io.EOF is returned when no more items are left. + Next() (item string, err error) +} + +type listIterator struct { + items []string + index int +} + +type readerIterator struct { + rd *bufio.Reader + sep byte +} + +// NewListIterator returns a new iterator from a list of items. +func NewListIterator(items []string) Iterator { + return &listIterator{items: items} +} + +func (l *listIterator) Next() (string, error) { + if l.index < len(l.items) { + l.index++ + return l.items[l.index-1], nil + } + return "", io.EOF +} + +// NewReaderIterator returns a new iterator from a reader. +func NewReaderIterator(rd io.Reader, sep byte) Iterator { + return &readerIterator{rd: bufio.NewReader(rd), sep: sep} +} + +func (r *readerIterator) Next() (string, error) { + s, err := r.rd.ReadString(r.sep) + if err == io.EOF && s != "" { + return s, nil + } + return strings.TrimSpace(s), err +} diff --git a/cmd/util_test.go b/pkg/commands/iterator_test.go similarity index 83% rename from cmd/util_test.go rename to pkg/commands/iterator_test.go index 3a68656..43809d5 100644 --- a/cmd/util_test.go +++ b/pkg/commands/iterator_test.go @@ -1,4 +1,4 @@ -package cmd_test +package commands_test import ( "bytes" @@ -8,11 +8,11 @@ import ( "github.com/smartystreets/assertions" "github.com/smartystreets/assertions/should" - "go.thethings.network/lorawan-stack-migrate/cmd" + "go.thethings.network/lorawan-stack-migrate/pkg/commands" ) func TestListIterator(t *testing.T) { - it := cmd.NewListIterator([]string{"one", "two", "three"}) + it := commands.NewListIterator([]string{"one", "two", "three"}) a := assertions.New(t) s, err := it.Next() @@ -36,7 +36,7 @@ func TestListIterator(t *testing.T) { func TestReaderIterator(t *testing.T) { for _, sep := range []string{"\n", "\r\n"} { buf := []byte(strings.Join([]string{"one", "two", "three"}, sep)) - it := cmd.NewReaderIterator(bytes.NewBuffer(buf), '\n') + it := commands.NewReaderIterator(bytes.NewBuffer(buf), '\n') a := assertions.New(t) s, err := it.Next() diff --git a/pkg/commands/source.go b/pkg/commands/source.go new file mode 100644 index 0000000..d3af11c --- /dev/null +++ b/pkg/commands/source.go @@ -0,0 +1,112 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "github.com/spf13/cobra" + "go.thethings.network/lorawan-stack-migrate/pkg/source" +) + +// Source returns a new source command. +func Source(sourceName, short string, opts ...Option) *cobra.Command { + fs, _ := source.FlagSet(sourceName) + + appCmd := Application( + WithFlagSet(fs), + WithPersistentPreRunE(ExecuteParentPersistentPreRun), + ) + devCmd := Devices( + WithFlagSet(fs), + WithPersistentPreRunE(ExecuteParentPersistentPreRun), + ) + + cmd := New(append(opts, + WithUse(sourceName+" ..."), + WithShort(short), + WithPersistentPreRunE(SourcePersistentPreRunE()), + WithSubcommands(appCmd, devCmd), + // TODO: After dependency update (https://github.com/TheThingsNetwork/lorawan-stack-migrate/issues/72) + // Add to "sources" group. + )...) + + return cmd +} + +// Application returns a new application command. +func Application(opts ...Option) *cobra.Command { + defaultOpts := []Option{ + WithUse("application ..."), + WithShort("Export all devices of an application"), + WithAliases([]string{"applications", "apps", "app", "a"}), + WithRun(ExportApplication()), + } + return New(append(defaultOpts, opts...)...) +} + +// Devices returns a new devices command. +func Devices(opts ...Option) *cobra.Command { + defaultOpts := []Option{ + WithUse("device ..."), + WithShort("Export devices by DevEUI"), + WithAliases([]string{"end-devices", "end-device", "devices", "devs", "dev", "d"}), + WithRunE(ExportDevices()), + } + return New(append(defaultOpts, opts...)...) +} + +// SourcePersistentPreRunE returns a new function that sets the active source. +func SourcePersistentPreRunE() CobraRunE { + return func(cmd *cobra.Command, args []string) error { + s := cmd.Name() + if ok := source.RootConfig.SetSource(s); !ok { + return source.ErrNotRegistered.WithAttributes("source", s).New() + } + return ExecuteParentPersistentPreRun(cmd, args) + } +} + +// ExecuteParentPersistentPreRun executes cmd's parent's PersistentPreRunE or PersistentPreRun. +func ExecuteParentPersistentPreRun(cmd *cobra.Command, args []string) error { + if !cmd.HasParent() { + return nil + } + p := cmd.Parent() + + if f := p.PersistentPreRunE; f != nil { + if err := f(p, args); err != nil { + return err + } + } else if f := p.PersistentPreRun; f != nil { + f(p, args) + } + return nil +} + +// ExecuteParentPersistentPostRun executes cmd's parent's PersistentPostRunE or PersistentPostRun. +func ExecuteParentPersistentPostRun(cmd *cobra.Command, args []string) error { + if !cmd.HasParent() { + return nil + } + p := cmd.Parent() + + if f := p.PersistentPostRunE; f != nil { + if err := f(p, args); err != nil { + return err + } + } else if f := p.PersistentPostRun; f != nil { + f(p, args) + } + return nil +} diff --git a/pkg/export/context.go b/pkg/export/context.go new file mode 100644 index 0000000..d92c722 --- /dev/null +++ b/pkg/export/context.go @@ -0,0 +1,33 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package export + +import "context" + +type exportConfigKeyType struct{} + +var exportConfigKey = &exportConfigKeyType{} + +func NewContext(ctx context.Context, cfg Config) context.Context { + return context.WithValue(ctx, exportConfigKey, cfg) +} + +func FromContext(ctx context.Context) Config { + v := ctx.Value(exportConfigKey) + if v == nil { + return Config{} + } + return v.(Config) +} diff --git a/pkg/export/errors.go b/pkg/export/errors.go new file mode 100644 index 0000000..dc416da --- /dev/null +++ b/pkg/export/errors.go @@ -0,0 +1,25 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package export + +import "go.thethings.network/lorawan-stack/v3/pkg/errors" + +var ( + errExport = errors.Define("export", "export device `{device_id}`") + errFormat = errors.DefineCorruption("format", "format device `{device_id}`") + errInvalidFields = errors.DefineInvalidArgument("invalid_fields", "invalid fields for device `{device_id}`") + errDevIDExceedsMaxLength = errors.Define("dev_id_exceeds_max_length", "device ID `{id}` exceeds max length") + errAppIDExceedsMaxLength = errors.Define("app_id_exceeds_max_length", "application ID `{id}` exceeds max length") +) diff --git a/pkg/export/export.go b/pkg/export/export.go new file mode 100644 index 0000000..cb9b99a --- /dev/null +++ b/pkg/export/export.go @@ -0,0 +1,89 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package export + +import ( + "fmt" + "os" + "strings" + + "go.thethings.network/lorawan-stack/v3/pkg/jsonpb" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + + "go.thethings.network/lorawan-stack-migrate/pkg/source" +) + +const ( + maxIDLength = 36 +) + +var sanitizeID = strings.NewReplacer("_", "-") + +func toJSON(dev *ttnpb.EndDevice) ([]byte, error) { + return jsonpb.TTN().Marshal(dev) +} + +type Config struct { + EUIForID bool + DevIDPrefix string +} + +func (cfg Config) ExportDev(s source.Source, devID string) error { + dev, err := s.ExportDevice(devID) + if err != nil { + return errExport.WithAttributes("device_id", devID).WithCause(err) + } + oldID := dev.Ids.DeviceId + + if eui := dev.Ids.DevEui; cfg.EUIForID && eui != nil { + dev.Ids.DeviceId = strings.ToLower(string(eui)) + } + if cfg.DevIDPrefix != "" { + dev.Ids.DeviceId = fmt.Sprintf("%s-%s", cfg.DevIDPrefix, dev.Ids.DeviceId) + } + + dev.Ids.DeviceId = sanitizeID.Replace(dev.Ids.DeviceId) + if id := dev.Ids.DeviceId; len(id) > maxIDLength { + return errDevIDExceedsMaxLength.WithAttributes("id", id) + } + + if dev.Ids.DeviceId != oldID { + if dev.Attributes == nil { + dev.Attributes = make(map[string]string) + } + dev.Attributes["old-id"] = oldID + } + + dev.Ids.ApplicationIds.ApplicationId = sanitizeID.Replace(dev.Ids.ApplicationIds.ApplicationId) + if id := dev.Ids.ApplicationIds.ApplicationId; len(id) > maxIDLength { + return errAppIDExceedsMaxLength.WithAttributes("id", id) + } + + if err := dev.ValidateFields(); err != nil { + return errInvalidFields.WithAttributes( + "device_id", dev.Ids.DeviceId, + "dev_eui", dev.Ids.DevEui, + ).WithCause(err) + } + b, err := toJSON(dev) + if err != nil { + return errFormat.WithAttributes( + "device_id", dev.Ids.DeviceId, + "dev_eui", dev.Ids.DevEui, + ).WithCause(err) + } + _, err = fmt.Fprintln(os.Stdout, string(b)) + return err +} diff --git a/pkg/source/errors.go b/pkg/source/errors.go new file mode 100644 index 0000000..54b14a9 --- /dev/null +++ b/pkg/source/errors.go @@ -0,0 +1,23 @@ +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package source + +import "go.thethings.network/lorawan-stack/v3/pkg/errors" + +var ( + ErrNotRegistered = errors.DefineInvalidArgument("not_registered", "source `{source}` is not registered") + ErrAlreadyRegistered = errors.DefineInvalidArgument("already_registered", "source `{source}` is already registered") + ErrNoSource = errors.DefineInvalidArgument("no_source", "no source") +) diff --git a/pkg/source/source.go b/pkg/source/source.go index fd4e530..e8ec2b6 100644 --- a/pkg/source/source.go +++ b/pkg/source/source.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,14 +20,26 @@ import ( "strings" "github.com/spf13/pflag" - "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" ) type Config struct { - DryRun, Verbose bool - FrequencyPlansURL, - Source string + DryRun, Verbose bool + FrequencyPlansURL string + + source string +} + +func (c *Config) SetSource(s string) bool { + if _, ok := registeredSources[s]; ok { + c.source = s + return true + } + return false +} + +func (c *Config) Source() string { + return c.source } var RootConfig Config @@ -54,18 +66,12 @@ type Registration struct { FlagSet *pflag.FlagSet } -var ( - errNotRegistered = errors.DefineInvalidArgument("not_registered", "source `{source}` is not registered") - errAlreadyRegistered = errors.DefineInvalidArgument("already_registered", "source `{source}` is already registered") - errNoSource = errors.DefineInvalidArgument("no_source", "no source") - - registeredSources map[string]Registration -) +var registeredSources map[string]Registration // RegisterSource registers a new Source. func RegisterSource(r Registration) error { if _, ok := registeredSources[r.Name]; ok { - return errAlreadyRegistered.WithAttributes("source", r.Name) + return ErrAlreadyRegistered.WithAttributes("source", r.Name) } registeredSources[r.Name] = r return nil @@ -73,13 +79,13 @@ func RegisterSource(r Registration) error { // NewSource creates a new Source from parsed flags. func NewSource(ctx context.Context) (Source, error) { - if RootConfig.Source == "" { - return nil, errNoSource.New() + if RootConfig.Source() == "" { + return nil, ErrNoSource.New() } - if registration, ok := registeredSources[RootConfig.Source]; ok { + if registration, ok := registeredSources[RootConfig.Source()]; ok { return registration.Create(ctx, RootConfig) } - return nil, errNotRegistered.WithAttributes("source", RootConfig.Source) + return nil, ErrNotRegistered.WithAttributes("source", RootConfig.Source()) } func addPrefix(name, prefix string) string { @@ -93,24 +99,38 @@ func addPrefix(name, prefix string) string { return prefix + name } -// FlagSet returns flags for all configured sources. -func FlagSet() *pflag.FlagSet { - flags := &pflag.FlagSet{} - names := []string{} +// AllFlagSets returns flags for all configured sources prefixed with source names. +func AllFlagSets() *pflag.FlagSet { + fs := new(pflag.FlagSet) for _, r := range registeredSources { - if r.FlagSet != nil { - r.FlagSet.VisitAll(func(f *pflag.Flag) { - f.Name = addPrefix(f.Name, r.Name) - }) - flags.AddFlagSet(r.FlagSet) - names = append(names, r.Name) + if r.FlagSet == nil { + continue } + r.FlagSet.VisitAll(func(a *pflag.Flag) { + b := *a // Avoid modifying source flags + b.Name = addPrefix(b.Name, r.Name) + b.Annotations = make(map[string][]string) + for k, v := range a.Annotations { + b.Annotations[k] = v + } + fs.AddFlag(&b) + }) } - flags.StringVar(&RootConfig.Source, + fs.StringVar(&RootConfig.source, "source", "", - fmt.Sprintf("source (%s)", strings.Join(Names(), "|"))) - return flags + fmt.Sprintf("source (%s)", strings.Join(Names(), "|")), + ) + return fs +} + +// FlagSet returns a flag set for a given source. +func FlagSet(s string) (*pflag.FlagSet, error) { + src, ok := registeredSources[s] + if !ok { + return nil, ErrNotRegistered.WithAttributes("source", s) + } + return src.FlagSet, nil } // Sources returns a map of registered Sources and their descriptions. diff --git a/pkg/source/ttnv2/source.go b/pkg/source/ttnv2/source.go index 1f4ba79..371b71a 100644 --- a/pkg/source/ttnv2/source.go +++ b/pkg/source/ttnv2/source.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/source/ttnv3/api/api.go b/pkg/source/tts/api/api.go similarity index 100% rename from pkg/source/ttnv3/api/api.go rename to pkg/source/tts/api/api.go diff --git a/pkg/source/ttnv3/config/config.go b/pkg/source/tts/config/config.go similarity index 90% rename from pkg/source/ttnv3/config/config.go rename to pkg/source/tts/config/config.go index 1119592..6a9eb0a 100644 --- a/pkg/source/ttnv3/config/config.go +++ b/pkg/source/tts/config/config.go @@ -17,13 +17,12 @@ package config import ( "crypto/tls" "crypto/x509" - "fmt" "net/http" "os" "github.com/spf13/pflag" "go.thethings.network/lorawan-stack-migrate/pkg/source" - "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv3/api" + "go.thethings.network/lorawan-stack-migrate/pkg/source/tts/api" "go.uber.org/zap" ) @@ -78,16 +77,16 @@ func New() (*Config, *pflag.FlagSet) { flags.StringVar(&config.AppID, "app-id", - os.Getenv("TTNV3_APP_ID"), + os.Getenv("TTS_APP_ID"), "TTS Application ID") flags.StringVar(&config.appAPIKey, "app-api-key", - os.Getenv("TTNV3_APP_API_KEY"), + os.Getenv("TTS_APP_API_KEY"), "TTS Application Access Key (with 'devices' permissions)") flags.StringVar(&config.caPath, "ca-file", - os.Getenv("TTNV3_CA_FILE"), + os.Getenv("TTS_CA_FILE"), "TTS Path to a CA file (optional)") flags.BoolVar(&config.insecure, "insecure", @@ -96,23 +95,23 @@ func New() (*Config, *pflag.FlagSet) { flags.StringVar(&config.ServerConfig.defaultGRPCAddress, "default-grpc-address", - os.Getenv("TTNV3_DEFAULT_GRPC_ADDRESS"), + os.Getenv("TTS_DEFAULT_GRPC_ADDRESS"), "TTS default GRPC Address (optional)") flags.StringVar(&config.ServerConfig.ApplicationServerGRPCAddress, "appplication-server-grpc-address", - os.Getenv("TTNV3_APPLICATION_SERVER_GRPC_ADDRESS"), + os.Getenv("TTS_APPLICATION_SERVER_GRPC_ADDRESS"), "TTS Application Server GRPC Address") flags.StringVar(&config.ServerConfig.IdentityServerGRPCAddress, "identity-server-grpc-address", - os.Getenv("TTNV3_IDENTITY_SERVER_GRPC_ADDRESS"), + os.Getenv("TTS_IDENTITY_SERVER_GRPC_ADDRESS"), "TTS Identity Server GRPC Address") flags.StringVar(&config.ServerConfig.JoinServerGRPCAddress, "join-server-grpc-address", - os.Getenv("TTNV3_JOIN_SERVER_GRPC_ADDRESS"), + os.Getenv("TTS_JOIN_SERVER_GRPC_ADDRESS"), "TTS Join Server GRPC Address") flags.StringVar(&config.ServerConfig.NetworkServerGRPCAddress, "network-server-grpc-address", - os.Getenv("TTNV3_NETWORK_SERVER_GRPC_ADDRESS"), + os.Getenv("TTS_NETWORK_SERVER_GRPC_ADDRESS"), "TTS Network Server GRPC Address") flags.BoolVar(&config.NoSession, @@ -213,7 +212,3 @@ func setCustomCA(path string) error { } return nil } - -func flagWithPrefix(f string) string { - return fmt.Sprintf("ttnv3.%s", f) -} diff --git a/pkg/source/ttnv3/config/errors.go b/pkg/source/tts/config/errors.go similarity index 100% rename from pkg/source/ttnv3/config/errors.go rename to pkg/source/tts/config/errors.go diff --git a/pkg/source/ttnv3/errors.go b/pkg/source/tts/errors.go similarity index 99% rename from pkg/source/ttnv3/errors.go rename to pkg/source/tts/errors.go index 4a72a3e..4d0eacf 100644 --- a/pkg/source/ttnv3/errors.go +++ b/pkg/source/tts/errors.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ttnv3 +package tts import "go.thethings.network/lorawan-stack/v3/pkg/errors" diff --git a/pkg/source/ttnv3/source.go b/pkg/source/tts/source.go similarity index 98% rename from pkg/source/ttnv3/source.go rename to pkg/source/tts/source.go index 94c7bed..9f5b00f 100644 --- a/pkg/source/ttnv3/source.go +++ b/pkg/source/tts/source.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ttnv3 +package tts import ( "context" "go.thethings.network/lorawan-stack-migrate/pkg/source" - "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv3/api" - "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv3/config" + "go.thethings.network/lorawan-stack-migrate/pkg/source/tts/api" + "go.thethings.network/lorawan-stack-migrate/pkg/source/tts/config" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.uber.org/zap" ) diff --git a/pkg/source/ttnv3/ttnv3.go b/pkg/source/tts/tts.go similarity index 89% rename from pkg/source/ttnv3/ttnv3.go rename to pkg/source/tts/tts.go index 1978122..8e12086 100644 --- a/pkg/source/ttnv3/ttnv3.go +++ b/pkg/source/tts/tts.go @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ttnv3 +package tts import ( "go.thethings.network/lorawan-stack-migrate/pkg/source" - "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv3/config" + "go.thethings.network/lorawan-stack-migrate/pkg/source/tts/config" ) func init() { @@ -25,7 +25,7 @@ func init() { logger, _ = config.NewLogger(cfg.Verbose) source.RegisterSource(source.Registration{ - Name: "ttnv3", + Name: "tts", Description: "Migrate from The Things Stack", FlagSet: flags, Create: createNewSource(cfg), diff --git a/pkg/source/ttnv3/util.go b/pkg/source/tts/util.go similarity index 99% rename from pkg/source/ttnv3/util.go rename to pkg/source/tts/util.go index 1969023..c3c4c43 100644 --- a/pkg/source/ttnv3/util.go +++ b/pkg/source/tts/util.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ttnv3 +package tts import ( "bytes" diff --git a/pkg/version/build.go b/pkg/version/build.go index 6e4fa7b..5c7c1a1 100644 --- a/pkg/version/build.go +++ b/pkg/version/build.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tools.go b/tools.go index 396e55e..a97dfcd 100644 --- a/tools.go +++ b/tools.go @@ -1,4 +1,4 @@ -// Copyright © 2020 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.