Skip to content

Commit 726442c

Browse files
committed
Add new parameters for interactive login, new params to export empty fields with reandmes adjusted.
1 parent 06921c1 commit 726442c

22 files changed

+797
-34
lines changed

Guide-XrmToolBox.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ This tool was created to support more data types that the official tool does.
99
✔️ Schema definition file for export/import
1010

1111
### Upcoming features 🔜
12-
🔜 Configuration Data Importation \
13-
🔜 Configuration Data Exportation
12+
🔜 Supports for multiselect optionset \
13+
🔜 ~~Configuration Data Importation~~ <span style="color:red">This is no longer planned.</span>\
14+
🔜 ~~Configuration Data Exportation~~ <span style="color:red">This is no longer planned.</span>
15+
16+
> [!IMPORTANT]
17+
> Data import/export features will only be available through the cli tool.
18+
> Since the cli tool is made with .Net Core and XTB Plugins is in .Net Framework, It's really hard to keep a sharable codebase and retricts the usage of some modern libraries.
19+
> CLI tool documentation [here](https://github.com/dotnetprog/dataverse-configuration-migration-tool)
1420
15-
>**Note**: Those features are available through the cli tool. More Info [here](https://github.com/dotnetprog/dataverse-configuration-migration-too)
1621

1722
## What's a schema definition file exactly 🤔❓
1823

README.md

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ This repository contains a custom .NET CLI tool designed to export and import co
55
### Download latest release
66
Get latest version of the tool built on this [release](https://github.com/dotnetprog/dataverse-configuration-migration-tool/releases/latest)
77
> [!NOTE]
8-
> If you want to use the built version of the tool , `appsettings.Production.json` will need to be setup manually with your azure service principal credentials.
8+
> Interactive Login is now available with the CLI tool. The use of service principal is no more mandatory but still recommanded for automation scenarios. \
9+
> To automate the use of the tool, let's say within a pipeline (e.g Github Action,Azure Devops Pipeline),`appsettings.Production.json` will need to be setup manually with your azure service principal credentials.
910
> [Quick Guide](https://recursion.no/blogs/dataverse-setup-service-principal-access-for-environment/) to create an azure service principal
1011
## Why ❓
1112

@@ -17,7 +18,8 @@ This new tool enables you to:
1718
- runs on windows and **linux**
1819

1920
## ⭐Features⭐
20-
21+
🆕 :heavy_check_mark: **Interactive Login** is now available by using `--il` parameter. Service Principal is no more required.🆕 \
22+
🆕 :heavy_check_mark: Data export now supports a new parameter `--AllowEmptyFields` or `--enable-empty-fields` to export empty fields. *Useful to clear values on target environments* 🆕 \
2123
:heavy_check_mark: Import configuration data into Dataverse \
2224
:heavy_check_mark: Export configuration data from Dataverse \
2325
:heavy_check_mark: Schema validation and rule-based checks \
@@ -36,6 +38,13 @@ This new tool enables you to:
3638
- Image
3739
- File
3840

41+
### Upcoming features 🔜
42+
🔜 Supports for multiselect optionset
43+
44+
45+
> [!IMPORTANT]
46+
> Data import/export features for XrmToolBox is no longer planned.
47+
> Since the cli tool is made with .Net Core and XTB Plugins is in .Net Framework, It's really hard to keep a sharable codebase and retricts the usage of some modern libraries.
3948
4049

4150

@@ -59,15 +68,20 @@ This new tool enables you to:
5968

6069
### Usage
6170

71+
Before running the tool, set your dataverse variables securely using [dotnet user-secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets):
6272

63-
Before running the tool, set your `clientId`, `clientSecret` and `url` securely using [dotnet user-secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets):
64-
73+
#### For Service principal
6574
```powershell
6675
cd src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console
6776
dotnet user-secrets set "Dataverse:ClientId" "<your-client-id>"
6877
dotnet user-secrets set "Dataverse:ClientSecret" "<your-client-secret>"
6978
dotnet user-secrets set "Dataverse:Url" "<your-env-url>"
7079
```
80+
#### For Interactive Login
81+
```powershell
82+
cd src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console
83+
dotnet user-secrets set "Dataverse:Url" "<your-env-url>"
84+
```
7185

7286
Run the CLI tool with the required arguments (no need to pass clientId or clientSecret on the command line):
7387
#### example
@@ -77,13 +91,51 @@ dotnet run --environment DOTNET_ENVIRONMENT=Development --project Dataverse.Conf
7791

7892
#### 💻 Command Line Arguments 💻
7993

94+
8095
Verb: `import`
81-
- `--data` : Path to the data xml file, you can use `export-data` command or the microsoft tool (see last section).
82-
- `--schema` : Path to the schema XML file
96+
- `--data` : Path to the data xml file, you can use `export-data` command or the microsoft tool (see last section).(Absolute or relative path)
97+
- `--schema` : Path to the schema XML file (Absolute or relative path)
98+
- 🆕`--il` or `-il` : **Optional** flag to use interactive login (user will be prompted to log in) instead of service principal. Useful for local testing/execution.
99+
100+
> [!NOTE]
101+
> using interactive login to import data will request user to login twice as it instantiates two different connections to dataverse for performance purposes.
102+
103+
**Example**
104+
105+
using dotnet run from the solution folder (.sln)
106+
```powershell
107+
dotnet run --environment DOTNET_ENVIRONMENT=Development --project Dataverse.ConfigurationMigrationTool.Console -- import --data "C:\temp\data.xml" --schema "C:\temp\data_schema.xml" --il
108+
```
109+
using directly the tool executable
110+
```powershell
111+
cd path/to/tool__executable_folder
112+
Dataverse.ConfigurationMigrationTool.Console.exe import --data "C:\temp\data.xml" --schema "C:\temp\data_schema.xml" --il
113+
```
83114

84115
Verb: `export-data`
85-
- `--schema` : Path to the schema XML file
86-
- `--output` : output file path to save the exported data. This file can be used for the `import` command.
116+
- `--schema` : Path to the schema XML file (Absolute or relative path)
117+
- `--output` : output file path to save the exported data. This file can be used for the `import` command. (Absolute or relative path)
118+
- 🆕`--AllowEmptyFields` or `--enable-empty-fields` : **Optional** flag to include fields with empty values in the export. Useful for clearing values in the target environment.
119+
- 🆕`--il` or `-il` : **Optional** flag to use interactive login (user will be prompted to log in) instead of service principal. Useful for local testing/execution.
120+
121+
**Example**
122+
123+
using dotnet run from the solution folder (.sln)
124+
```powershell
125+
dotnet run --environment DOTNET_ENVIRONMENT=Development --project Dataverse.ConfigurationMigrationTool.Console -- export-data --schema "C:\temp\data_schema.xml" --output "C:\temp\exported_data.xml" --enable-empty-fields --il
126+
```
127+
using directly the tool executable
128+
```powershell
129+
cd path/to/tool__executable_folder
130+
Dataverse.ConfigurationMigrationTool.Console.exe export-data --schema "C:\temp\data_schema.xml" --output "C:\temp\exported_data.xml" --enable-empty-fields --il
131+
```
132+
133+
> [!TIP]
134+
> To use service principal credentials, the interactive login flag `--il` or `-il` must be omitted.
135+
> If you use the executable directly, you must edit the `appsettings.Production.json` file with your service principal credentials
136+
> If you use `dotnet run`, you can set the secrets using `dotnet user-secrets` as shown above or edit the `appsettings.Development.json` file.
137+
> you can also set environment variables `Dataverse__ClientId`, `Dataverse__ClientSecret` and `Dataverse__Url` to override the settings in the json files.
138+
> you can also use these commandline arguments `--Dataverse:ClientId`, `--Dataverse:ClientSecret` and `--Dataverse:Url` to override the settings in the json files.
87139
88140
## 🤝 Contributing 🤝
89141

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.ConfigurationProviders;
2+
using Microsoft.Extensions.Configuration;
3+
using NSubstitute;
4+
5+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.ConfigurationProviders;
6+
public class CustomCommandLineConfigurationExtensionsTests
7+
{
8+
private readonly IConfigurationBuilder _builder = Substitute.For<IConfigurationBuilder>();
9+
public CustomCommandLineConfigurationExtensionsTests()
10+
{
11+
_builder.Add(Arg.Any<IConfigurationSource>()).Returns(_builder);
12+
}
13+
[Fact]
14+
public void GivenAConfigurationBuilder_WhenItAddsCustomCommandline_ThenItAddsTheCustomCommandLineConfigurationProvider()
15+
{
16+
// Arrange
17+
var args = new[] { "--key=value" };
18+
// Act
19+
_builder.AddCustomCommandline(args);
20+
// Assert
21+
_builder.Received(1).Add(Arg.Is<CustomCommandLineConfigurationSource>(source =>
22+
source.Args.SequenceEqual(args) &&
23+
source.SwitchMappings == null &&
24+
source.FlagMappings == null));
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.ConfigurationProviders;
2+
using Shouldly;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.ConfigurationProviders;
5+
6+
public class CustomCommandLineConfigurationProviderTests
7+
{
8+
[Fact]
9+
public void Given_ArgsWithDoubleDash_When_Load_Then_ParsesKeyValueCorrectly()
10+
{
11+
// Arrange
12+
var args = new[] { "--key=value" };
13+
var provider = BuildProvider(args);
14+
// Act
15+
provider.Load();
16+
// Assert
17+
GetValue(provider, "key").ShouldBe("value");
18+
}
19+
[Fact]
20+
public void Given_ArgsWithDoubleDashWithNoSeparator_When_Load_Then_ParsesKeyValueCorrectly()
21+
{
22+
// Arrange
23+
var args = new[] { "--key", "value" };
24+
var provider = BuildProvider(args);
25+
// Act
26+
provider.Load();
27+
// Assert
28+
GetValue(provider, "key").ShouldBe("value");
29+
}
30+
[Fact]
31+
public void Given_ArgsWithSingleDashWithNoSeparator_When_Load_Then_ParsesKeyValueCorrectly()
32+
{
33+
// Arrange
34+
var args = new[] { "-key", "value" };
35+
var switchMappings = new Dictionary<string, string> { { "-key", "key" } };
36+
var provider = BuildProvider(args, switchMappings);
37+
// Act
38+
provider.Load();
39+
// Assert
40+
GetValue(provider, "key").ShouldBe("value");
41+
}
42+
[Fact]
43+
public void Given_ArgsWithSingleDash_When_Load_Then_ParsesKeyValueCorrectly()
44+
{
45+
// Arrange
46+
var args = new[] { "-key=value" };
47+
var switchMappings = new Dictionary<string, string> { { "-key", "key" } };
48+
var provider = BuildProvider(args, switchMappings);
49+
// Act
50+
provider.Load();
51+
// Assert
52+
GetValue(provider, "key").ShouldBe("value");
53+
}
54+
55+
[Fact]
56+
public void Given_ArgsWithSlash_When_Load_Then_ParsesKeyValueCorrectly()
57+
{
58+
// Arrange
59+
var args = new[] { "/key=value" };
60+
var provider = BuildProvider(args);
61+
// Act
62+
provider.Load();
63+
// Assert
64+
GetValue(provider, "key").ShouldBe("value");
65+
}
66+
67+
[Fact]
68+
public void Given_ArgsWithSwitchMappings_When_Load_Then_MapsSwitchToCustomKey()
69+
{
70+
// Arrange
71+
var args = new[] { "--customSwitch=value" };
72+
var switchMappings = new Dictionary<string, string> { { "--customSwitch", "mappedKey" } };
73+
var provider = BuildProvider(args, switchMappings);
74+
// Act
75+
provider.Load();
76+
// Assert
77+
GetValue(provider, "mappedKey").ShouldBe("value");
78+
}
79+
80+
[Fact]
81+
public void Given_ArgsWithFlagMappings_When_Load_Then_SetsFlagValueToTrue()
82+
{
83+
// Arrange
84+
var args = new[] { "--flag" };
85+
var flagMappings = new[] { "--flag" };
86+
var provider = BuildProvider(args, null, flagMappings);
87+
// Act
88+
provider.Load();
89+
// Assert
90+
GetValue(provider, "flag").ShouldBe("True");
91+
}
92+
93+
[Fact]
94+
public void Given_ArgsWithDuplicateKeys_When_Load_Then_LastValueWins()
95+
{
96+
// Arrange
97+
var args = new[] { "--key=first", "--key=second" };
98+
var provider = BuildProvider(args);
99+
// Act
100+
provider.Load();
101+
// Assert
102+
GetValue(provider, "key").ShouldBe("second");
103+
}
104+
105+
[Fact]
106+
public void Given_ArgsWithInvalidFormat_When_Load_Then_IgnoresInvalidArgs()
107+
{
108+
// Arrange
109+
var args = new[] { "invalidArg", "--valid=value" };
110+
var provider = BuildProvider(args);
111+
// Act
112+
provider.Load();
113+
// Assert
114+
GetValue(provider, "invalidArg").ShouldBeNull();
115+
GetValue(provider, "valid").ShouldBe("value");
116+
}
117+
118+
[Fact]
119+
public void Given_ShortDashWithoutMapping_When_Load_Then_IgnoresArg()
120+
{
121+
// Arrange
122+
var args = new[] { "-short" };
123+
var provider = BuildProvider(args);
124+
// Act
125+
provider.Load();
126+
// Assert
127+
GetValue(provider, "short").ShouldBeNull();
128+
}
129+
130+
[Fact]
131+
public void Given_ShortDashWithMapping_When_Load_Then_MapsKey()
132+
{
133+
// Arrange
134+
var args = new[] { "-s=value" };
135+
var switchMappings = new Dictionary<string, string> { { "-s", "shortKey" } };
136+
var provider = BuildProvider(args, switchMappings);
137+
// Act
138+
provider.Load();
139+
// Assert
140+
GetValue(provider, "shortKey").ShouldBe("value");
141+
}
142+
143+
[Fact]
144+
public void Given_ShortDashWithNoMappingAndEquals_When_Load_Then_ThrowsFormatException()
145+
{
146+
// Arrange
147+
var args = new[] { "-s=value" };
148+
var provider = BuildProvider(args);
149+
// Act
150+
var act = () => provider.Load();
151+
// Assert
152+
act.ShouldThrow<FormatException>();
153+
}
154+
155+
[Fact]
156+
public void Given_SwitchMappingsWithInvalidKey_When_Construct_Then_ThrowsArgumentException()
157+
{
158+
// Arrange
159+
var switchMappings = new Dictionary<string, string> { { "invalidKey", "mappedKey" } };
160+
var args = new[] { "--key=value" };
161+
// Act
162+
var act = () => BuildProvider(args, switchMappings);
163+
// Assert
164+
act.ShouldThrow<ArgumentException>();
165+
}
166+
167+
[Fact]
168+
public void Given_SwitchMappingsWithDuplicateKeys_When_Construct_Then_ThrowsArgumentException()
169+
{
170+
// Arrange
171+
var switchMappings = new Dictionary<string, string>
172+
{
173+
{ "--dup", "key1" },
174+
{ "--DUP", "key2" }
175+
};
176+
var args = new[] { "--dup=value" };
177+
// Act
178+
var act = () => BuildProvider(args, switchMappings);
179+
// Assert
180+
act.ShouldThrow<ArgumentException>();
181+
}
182+
183+
[Fact]
184+
public void Given_ArgsIsNull_When_Construct_Then_ThrowsArgumentNullException()
185+
{
186+
// Act
187+
var act = () => BuildProvider(null!);
188+
//Assert
189+
act.ShouldThrow<ArgumentNullException>();
190+
}
191+
private static CustomCommandLineConfigurationProvider BuildProvider(IEnumerable<string> args, IDictionary<string, string>? switchMappings = null, IEnumerable<string>? flagMappings = null)
192+
{
193+
return new CustomCommandLineConfigurationProvider(args, switchMappings, flagMappings);
194+
}
195+
196+
// Helper to get value from provider's TryGet method using reflection
197+
private static string? GetValue(CustomCommandLineConfigurationProvider provider, string key)
198+
{
199+
if (provider.TryGet(key, out var value))
200+
{
201+
return value;
202+
}
203+
return null;
204+
}
205+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.ConfigurationProviders;
2+
using Shouldly;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Tests.ConfigurationProviders;
5+
public class CustomCommandLineConfigurationSourceTests
6+
{
7+
private CustomCommandLineConfigurationSource ConfigSource { get; }
8+
public CustomCommandLineConfigurationSourceTests()
9+
{
10+
ConfigSource = new CustomCommandLineConfigurationSource()
11+
{
12+
Args = [],
13+
};
14+
}
15+
16+
[Fact]
17+
public void GivenACommandLineSource_WhenItBuildsAConfigProvider_ThenACommandLineConfigurationProviderIsReturned()
18+
{
19+
// Act
20+
var provider = ConfigSource.Build(null);
21+
// Assert
22+
provider.ShouldBeOfType<CustomCommandLineConfigurationProvider>();
23+
}
24+
25+
}

0 commit comments

Comments
 (0)