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

Initial support for generating a configuration file from machine configuration #2466

Merged
merged 21 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = [];
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
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
21 changes: 19 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,15 @@
<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
Loading