Skip to content

Commit

Permalink
Initial support for generating a configuration file from machine conf…
Browse files Browse the repository at this point in the history
…iguration (#2466)
  • Loading branch information
AmelBawa-msft committed Apr 4, 2024
1 parent 356f4ef commit 29c3ce6
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 135 deletions.
10 changes: 9 additions & 1 deletion tools/SetupFlow/DevHome.SetupFlow/Models/ISetupTaskGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,18 @@ public interface ISetupTaskGroup
public ReviewTabViewModelBase GetReviewTabViewModel();

/// <summary>
/// Gets all the individual setup tasks that make up this group
/// Gets all the setup tasks that make up this group
/// </summary>
public IEnumerable<ISetupTask> SetupTasks
{
get;
}

/// <summary>
/// Gets all the DSC tasks that make up this group
/// </summary>
public IEnumerable<ISetupTask> DSCTasks
{
get;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using YamlDotNet.Core;
using YamlDotNet.Serialization;

namespace DevHome.SetupFlow.Models.WingetConfigure;

/// <summary>
Expand All @@ -9,6 +12,7 @@ namespace DevHome.SetupFlow.Models.WingetConfigure;
/// </summary>
public class WinGetDscSettings : WinGetConfigSettingsBase
{
[YamlMember(ScalarStyle = ScalarStyle.DoubleQuoted)]
public string Id { get; set; }

public string Source { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using DevHome.SetupFlow.Models;
using DevHome.SetupFlow.Models.WingetConfigure;
using DevHome.SetupFlow.TaskGroups;
using Serilog;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

Expand All @@ -21,13 +22,8 @@ public enum ConfigurationFileKind

public class ConfigurationFileBuilder
{
private readonly SetupFlowOrchestrator _orchestrator;

public ConfigurationFileBuilder(SetupFlowOrchestrator orchestrator)
{
_orchestrator = orchestrator;
}

private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ConfigurationFileBuilder));

/// <summary>
/// Builds an object that represents a config file that can be used by WinGet Configure to install
/// apps and clone repositories.This is already formatted as valid yaml and can be written
Expand All @@ -36,21 +32,33 @@ public ConfigurationFileBuilder(SetupFlowOrchestrator orchestrator)
/// <returns>The config file object representing the yaml file.</returns>
public WinGetConfigFile BuildConfigFileObjectFromTaskGroups(IList<ISetupTaskGroup> taskGroups, ConfigurationFileKind configurationFileKind)
{
var listOfResources = new List<WinGetConfigResource>();

List<WinGetConfigResource> repoResources = [];
List<WinGetConfigResource> appResources = [];
foreach (var taskGroup in taskGroups)
{
if (taskGroup is RepoConfigTaskGroup repoConfigGroup)
{
// Add the GitDSC resource blocks to yaml
listOfResources.AddRange(GetResourcesForCloneTaskGroup(repoConfigGroup, configurationFileKind));
repoResources.AddRange(GetResourcesForCloneTaskGroup(repoConfigGroup, configurationFileKind));
}
else if (taskGroup is AppManagementTaskGroup appManagementGroup)
{
// Add the WinGetDsc resource blocks to yaml
listOfResources.AddRange(GetResourcesForAppManagementTaskGroup(appManagementGroup, configurationFileKind));
appResources.AddRange(GetResourcesForAppManagementTaskGroup(appManagementGroup, configurationFileKind));
}
}

// If Git is not added to the apps to install and there are
// repositories to clone, add Git as a pre-requisite
var isGitAdded = appResources
.Select(r => r.Settings as WinGetDscSettings)
.Any(s => s.Id == DscHelpers.GitWinGetPackageId);
if (!isGitAdded && repoResources.Count > 0)
{
appResources.Add(CreateWinGetInstallForGitPreReq());
}

List<WinGetConfigResource> listOfResources = [..appResources, ..repoResources];

if (listOfResources.Count == 0)
{
Expand Down Expand Up @@ -114,22 +122,24 @@ public string SerializeWingetFileObjectToString(WinGetConfigFile configFile)
private List<WinGetConfigResource> GetResourcesForCloneTaskGroup(RepoConfigTaskGroup repoConfigGroup, ConfigurationFileKind configurationFileKind)
{
var listOfResources = new List<WinGetConfigResource>();
var repoConfigTasks = repoConfigGroup.SetupTasks
var repoConfigTasks = repoConfigGroup.DSCTasks
.Where(task => task is CloneRepoTask)
.Select(task => task as CloneRepoTask)
.ToList();

if (repoConfigTasks.Count != 0)
{
listOfResources.Add(CreateWinGetInstallForGitPreReq());
}

foreach (var repoConfigTask in repoConfigTasks)
{
if (repoConfigTask.RepositoryToClone is GenericRepository genericRepository)
{
listOfResources.Add(CreateResourceFromTaskForGitDsc(repoConfigTask, genericRepository.RepoUri, configurationFileKind));
}
try
{
if (!repoConfigTask.RepositoryToClone.IsPrivate)
{
listOfResources.Add(CreateResourceFromTaskForGitDsc(repoConfigTask, repoConfigTask.RepositoryToClone.RepoUri, configurationFileKind));
}
}
catch (Exception e)
{
_log.Error($"Error creating a repository resource entry", e);
}
}

return listOfResources;
Expand All @@ -143,7 +153,7 @@ private List<WinGetConfigResource> GetResourcesForCloneTaskGroup(RepoConfigTaskG
private List<WinGetConfigResource> GetResourcesForAppManagementTaskGroup(AppManagementTaskGroup appManagementGroup, ConfigurationFileKind configurationFileKind)
{
var listOfResources = new List<WinGetConfigResource>();
var installList = appManagementGroup.SetupTasks
var installList = appManagementGroup.DSCTasks
.Where(task => task is InstallPackageTask)
.Select(task => task as InstallPackageTask)
.ToList();
Expand Down Expand Up @@ -177,8 +187,16 @@ private WinGetConfigResource CreateResourceFromTaskForWinGetDsc(InstallPackageTa
{
Resource = DscHelpers.WinGetDscResource,
Id = id,
Directives = new() { AllowPrerelease = true, Description = $"Installing {arguments.PackageId}" },
Settings = new WinGetDscSettings() { Id = arguments.PackageId, Source = DscHelpers.DscSourceNameForWinGet },
Directives = new()
{
AllowPrerelease = true,
Description = $"Installing {arguments.PackageId}",
},
Settings = new WinGetDscSettings()
{
Id = arguments.PackageId,
Source = arguments.CatalogName,
},
};
}

Expand All @@ -190,16 +208,13 @@ private WinGetConfigResource CreateResourceFromTaskForWinGetDsc(InstallPackageTa
/// <returns>The WinGetConfigResource object that represents the block of yaml needed by GitDsc to clone the repository. </returns>
private WinGetConfigResource CreateResourceFromTaskForGitDsc(CloneRepoTask task, Uri webAddress, ConfigurationFileKind configurationFileKind)
{
// For normal cases, the Id will be null. This can be changed in the future when a use case for this Dsc File builder is needed outside the setup
// setup target flow. We can likely drop the if statement and just use whats in its body.
string id = null;
// WinGet configure uses the Id property to uniquely identify a resource and also to display the resource status in the UI.
// So we add a description to the Id to make it more readable in the UI. These do not need to be localized.
var id = $"Clone {task.RepositoryName}: {task.CloneLocation.FullName}";
var gitDependsOnId = DscHelpers.GitWinGetPackageId;

if (configurationFileKind == ConfigurationFileKind.SetupTarget)
{
// WinGet configure uses the Id property to uniquely identify a resource and also to display the resource status in the UI.
// So we add a description to the Id to make it more readable in the UI. These do not need to be localized.
id = $"Clone {task.RepositoryName}" + ": " + task.CloneLocation.FullName;
gitDependsOnId = $"{DscHelpers.GitWinGetPackageId} | Install: {DscHelpers.GitName}";
}

Expand All @@ -223,7 +238,7 @@ private WinGetConfigResource CreateWinGetInstallForGitPreReq()
return new WinGetConfigResource()
{
Resource = DscHelpers.WinGetDscResource,
Id = $"{DscHelpers.GitWinGetPackageId} | Install: {DscHelpers.GitName}",
Id = DscHelpers.GitWinGetPackageId,
Directives = new() { AllowPrerelease = true, Description = $"Installing {DscHelpers.GitName}" },
Settings = new WinGetDscSettings() { Id = DscHelpers.GitWinGetPackageId, Source = DscHelpers.DscSourceNameForWinGet },
};
Expand Down
28 changes: 24 additions & 4 deletions tools/SetupFlow/DevHome.SetupFlow/Services/PackageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private sealed class PackageCache
/// <summary>
/// Occurs when a package selection has changed
/// </summary>
public event EventHandler<PackageViewModel> PackageSelectionChanged;
public event EventHandler SelectedPackagesItemChanged;

public PackageProvider(PackageViewModelFactory packageViewModelFactory)
{
Expand Down Expand Up @@ -107,7 +107,7 @@ public PackageViewModel CreateOrGet(IWinGetPackage package, bool cachePermanentl
_log.Debug($"Creating view model for package [{package.Id}]");
var viewModel = _packageViewModelFactory(package);
viewModel.SelectionChanged += OnPackageSelectionChanged;
viewModel.SelectionChanged += (sender, package) => PackageSelectionChanged?.Invoke(sender, package);
viewModel.VersionChanged += OnSelectedPackageVersionChanged;

// Cache if requested
if (cachePermanently)
Expand All @@ -122,10 +122,25 @@ public PackageViewModel CreateOrGet(IWinGetPackage package, bool cachePermanentl

return viewModel;
}
}

private void OnSelectedPackageVersionChanged(object sender, string version)
{
var packageViewModel = sender as PackageViewModel;
if (packageViewModel?.IsSelected == true)
{
// Notify subscribers that an item in the list of selected packages has changed
SelectedPackagesItemChanged?.Invoke(packageViewModel, EventArgs.Empty);
}
}

public void OnPackageSelectionChanged(object sender, PackageViewModel packageViewModel)
{
private void OnPackageSelectionChanged(object sender, bool isSelected)
{
if (sender is not PackageViewModel packageViewModel)
{
return;
}

lock (_lock)
{
if (packageViewModel.IsSelected)
Expand Down Expand Up @@ -154,12 +169,17 @@ public void OnPackageSelectionChanged(object sender, PackageViewModel packageVie
{
_log.Debug($"Removing package [{packageViewModel.Package.Id}] from cache");
_packageViewModelCache.Remove(packageViewModel.UniqueKey);
packageViewModel.SelectionChanged -= OnPackageSelectionChanged;
packageViewModel.VersionChanged -= OnSelectedPackageVersionChanged;
}

// Remove from the selected package collection
_selectedPackages.Remove(packageViewModel);
}
}

// Notify subscribers that an item in the list of selected packages has changed
SelectedPackagesItemChanged?.Invoke(packageViewModel, EventArgs.Empty);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static class StringResourceKey
public static readonly string EditClonePathDialog = nameof(EditClonePathDialog);
public static readonly string EditClonePathDialogUncheckCheckMark = nameof(EditClonePathDialogUncheckCheckMark);
public static readonly string FilePickerFileTypeOption = nameof(FilePickerFileTypeOption);
public static readonly string FilePickerSingleFileTypeOption = nameof(FilePickerSingleFileTypeOption);
public static readonly string FileTypeNotSupported = nameof(FileTypeNotSupported);
public static readonly string InstalledPackage = nameof(InstalledPackage);
public static readonly string InstalledPackageReboot = nameof(InstalledPackageReboot);
Expand Down
20 changes: 18 additions & 2 deletions tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,11 @@
</data>
<data name="FilePickerFileTypeOption" xml:space="preserve">
<value>{0} files</value>
<comment>Dropdown option for a file picker. {0} is replaced by a file type (e.g. JSON, YAML, etc ...)</comment>
<comment>{Locked="{0}"}Dropdown option for a file picker. {0} is replaced by a file type (e.g. JSON, YAML, etc ...)</comment>
</data>
<data name="FilePickerSingleFileTypeOption" xml:space="preserve">
<value>{0} file</value>
<comment>{Locked="{0}"}Dropdown option for a file picker. {0} is replaced by a file type (e.g. JSON, YAML, etc ...)</comment>
</data>
<data name="FileTypeNotSupported" xml:space="preserve">
<value>File type not supported</value>
Expand Down Expand Up @@ -522,7 +526,7 @@
<comment>Header text for a group of controls giving multiple choices for configuring the machine, but not a full setup flow</comment>
</data>
<data name="MainPage_SetupFlow.Description" xml:space="preserve">
<value>Clone repositories and install applications at once</value>
<value>Clone repositories, install applications, and generate Winget Configuration files together</value>
<comment>Body text description for a card than when clicked takes the user to a multi-step flow for setting up their machine</comment>
</data>
<data name="MainPage_SetupFlow.Header" xml:space="preserve">
Expand Down Expand Up @@ -561,6 +565,10 @@
<value>Remove all</value>
<comment>Label for removing all items from selection</comment>
</data>
<data name="AppAlreadyInstalledNotification.Text" xml:space="preserve">
<value>Applications that have been previously installed cannot be installed again. They will be included in your generated configuration files.</value>
<comment>Message displayed when a user selects an application that is already installed</comment>
</data>
<data name="RemoveApplication" xml:space="preserve">
<value>Remove</value>
<comment>Text announced when screen readers focus on the 'Remove' button. The 'Remove' button allows users to remove an application from their cart</comment>
Expand Down Expand Up @@ -633,6 +641,14 @@
<value>Restore</value>
<comment>Label for restore button</comment>
</data>
<data name="Review_GenerateConfigurationFileButton.Content" xml:space="preserve">
<value>Generate Configuration file</value>
<comment>Text for a generating configuration file button</comment>
</data>
<data name="Review_DownloadFileTooltip.Text" xml:space="preserve">
<value>Generate a WinGet Configuration file (.winget) to repeat this set up in the future or share it with others.</value>
<comment>{Locked="WinGet",".winget"}Tooltip text about the generated configuration file</comment>
</data>
<data name="Review_SetupDetails.Text" xml:space="preserve">
<value>Set up details</value>
<comment>Header for a section detailing the set up steps to be performed. "Set up" is the noun</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
x:Uid="ms-resource:///DevHome.SetupFlow/Resources/Installed"/>
</converters:BoolToObjectConverter.TrueValue>
</converters:BoolToObjectConverter>
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:BoolToObjectConverter x:Name="SelectButtonGlyphConverter" TrueValue="&#xE845;" FalseValue="&#xE710;" />
</ResourceDictionary>
</Grid.Resources>
Expand All @@ -50,7 +49,6 @@
<VisualStateGroup x:Name="CommonStates">
<VisualState>
<VisualState.StateTriggers>
<StateTrigger IsActive="{Binding CanSelect, Converter={StaticResource BoolNegationConverter}}" />
<StateTrigger IsActive="{Binding IsSelected}" />
</VisualState.StateTriggers>
<VisualState.Setters>
Expand Down Expand Up @@ -93,7 +91,6 @@
SelectedItem="{Binding SelectedVersion, Mode=TwoWay}"
ItemsSource="{Binding AvailableVersions}" />
<Button
IsEnabled="{Binding CanSelect}"
AutomationProperties.Name="{Binding ButtonAutomationName}"
Padding="5"
Command="{Binding ToggleSelectionCommand,Mode=OneWay}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ public AppManagementTaskGroup(
_appManagementReviewViewModel = appManagementReviewViewModel;
}

public IEnumerable<ISetupTask> SetupTasks => _packageProvider.SelectedPackages.Select(sp => sp.InstallPackageTask);
public IEnumerable<ISetupTask> SetupTasks => _packageProvider.SelectedPackages
.Where(sp => sp.CanInstall)
.Select(sp => sp.InstallPackageTask);

public IEnumerable<ISetupTask> DSCTasks => _packageProvider.SelectedPackages
.Select(sp => sp.InstallPackageTask);

public SetupPageViewModelBase GetSetupPageViewModel() => _appManagementViewModel;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public ConfigurationFileTaskGroup(ConfigurationFileViewModel configurationFileVi
public async Task<bool> LoadFromLocalFileAsync(StorageFile file) => await _viewModel.LoadFileAsync(file);

public IEnumerable<ISetupTask> SetupTasks => _viewModel.TaskList;


public IEnumerable<ISetupTask> DSCTasks => SetupTasks;

/// <summary>
/// Gets the task corresponding to the configuration file to apply
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public IEnumerable<ISetupTask> SetupTasks
}
}

public IEnumerable<ISetupTask> DSCTasks => SetupTasks;

public SetupPageViewModelBase GetSetupPageViewModel() => null;

// Only show this tab when actually creating a dev drive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public RepoConfigTaskGroup(IHost host, ISetupFlowStringResource stringResource,
/// </summary>
public IEnumerable<ISetupTask> SetupTasks => CloneTasks;

public IEnumerable<ISetupTask> DSCTasks => SetupTasks;

/// <summary>
/// Gets all tasks that need to be ran.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public SetupTargetTaskGroup(

public IEnumerable<ISetupTask> SetupTasks => new List<ISetupTask>() { _setupTargetTaskGroup };

public IEnumerable<ISetupTask> DSCTasks => SetupTasks;

public SetupPageViewModelBase GetSetupPageViewModel() => _setupTargetViewModel;

public ReviewTabViewModelBase GetReviewTabViewModel() => _setupTargetReviewViewModel;
Expand Down
Loading

0 comments on commit 29c3ce6

Please sign in to comment.