forked from DataDog/datadog-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mutually exclusive MSI flavors (DataDog#32752)
- Loading branch information
Showing
5 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
test/new-e2e/tests/windows/install-test/mutually_exclusive_product_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Unless explicitly stated otherwise all files in this repository are licensed | ||
// under the Apache License Version 2.0. | ||
// This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
// Copyright 2016-present Datadog, Inc. | ||
|
||
package installtest | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/environments" | ||
"github.com/DataDog/datadog-agent/test/new-e2e/tests/windows" | ||
windowsCommon "github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/common" | ||
windowsAgent "github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/common/agent" | ||
|
||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
type mutuallyExclusiveInstallSuite struct { | ||
windows.BaseAgentInstallerSuite[environments.WindowsHost] | ||
|
||
previousAgentPackage *windowsAgent.Package | ||
} | ||
|
||
// TestFIPSAgentDoesNotInstallOverAgent tests that the FIPS agent cannot be installed over the base Agent | ||
// | ||
// This test uses the last stable base Agent package and the pipeline produced FIPS Agent package. | ||
func TestFIPSAgentDoesNotInstallOverAgent(t *testing.T) { | ||
s := &mutuallyExclusiveInstallSuite{} | ||
os.Setenv(windowsAgent.PackageFlavorEnvVar, "base") | ||
previousAgentPackage, err := windowsAgent.GetLastStablePackageFromEnv() | ||
require.NoError(t, err, "should get last stable agent package from env") | ||
s.previousAgentPackage = previousAgentPackage | ||
os.Setenv(windowsAgent.PackageFlavorEnvVar, "fips") | ||
run(t, s) | ||
} | ||
|
||
// TestAgentDoesNotInstallOverFIPSAgent tests that the base Agent cannot be installed over the FIPS agent | ||
// | ||
// This test uses the pipeline produced MSI packages for both flavors. This is necessary for now | ||
// because the previous Agent versions do not contain the changes to detect mutually exclusive products. | ||
func TestAgentDoesNotInstallOverFIPSAgent(t *testing.T) { | ||
s := &mutuallyExclusiveInstallSuite{} | ||
os.Setenv(windowsAgent.PackageFlavorEnvVar, "fips") | ||
previousAgentPackage, err := windowsAgent.GetPackageFromEnv() | ||
require.NoError(t, err, "should get Agent package from env") | ||
s.previousAgentPackage = previousAgentPackage | ||
os.Setenv(windowsAgent.PackageFlavorEnvVar, "base") | ||
run(t, s) | ||
} | ||
|
||
func (s *mutuallyExclusiveInstallSuite) SetupSuite() { | ||
// Base looks up the first Agent package | ||
s.BaseAgentInstallerSuite.SetupSuite() | ||
host := s.Env().RemoteHost | ||
var err error | ||
|
||
s.T().Logf("Using previous Agent package: %#vvi", s.previousAgentPackage) | ||
|
||
// Install first Agent | ||
_, err = s.InstallAgent(host, windowsAgent.WithPackage(s.previousAgentPackage)) | ||
s.Require().NoError(err) | ||
} | ||
|
||
func (s *mutuallyExclusiveInstallSuite) TestMutuallyExclusivePackage() { | ||
host := s.Env().RemoteHost | ||
|
||
// Install second Agent | ||
logFilePath := filepath.Join(s.SessionOutputDir(), "secondInstall.log") | ||
_, err := s.InstallAgent(host, | ||
windowsAgent.WithPackage(s.AgentPackage), | ||
windowsAgent.WithInstallLogFile(logFilePath), | ||
) | ||
s.Require().Error(err) | ||
|
||
// Ensure that the log file contains the expected error message | ||
logData, err := os.ReadFile(logFilePath) | ||
s.Require().NoError(err) | ||
// convert from utf-16 to utf-8 | ||
logData, err = windowsCommon.ConvertUTF16ToUTF8(logData) | ||
s.Require().NoError(err) | ||
// We don't use assert.Contains because it will print the very large logData on error | ||
s.Assert().True(strings.Contains(string(logData), "This product cannot be installed at the same time as ")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
tools/windows/DatadogAgentInstaller/WixSetup/MutuallyExclusiveProduct.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using System; | ||
using System.Xml.Linq; | ||
using WixSharp; | ||
|
||
namespace WixSetup | ||
{ | ||
internal class MutuallyExclusiveProducts : WixEntity, IGenericEntity | ||
{ | ||
private static int usageCounter = 0; | ||
|
||
public Guid UpgradeCode { get; set; } | ||
public string ProductName { get; set; } | ||
|
||
public MutuallyExclusiveProducts() | ||
{ | ||
} | ||
|
||
public MutuallyExclusiveProducts(string productName, Guid upgradeCode) | ||
{ | ||
UpgradeCode = upgradeCode; | ||
ProductName = productName; | ||
} | ||
|
||
/// <summary> | ||
/// Adds elements to the WiX to enforce that the product cannot be installed at the same time as another product. | ||
/// </summary> | ||
/// <remarks> | ||
/// The FindRelatedProducts action will set a property if a product matching the provided UpgradeCode | ||
/// is found on the system. We check this property in a LaunchCondition to prevent installation. | ||
/// See https://learn.microsoft.com/en-us/windows/win32/msi/findrelatedproducts-action | ||
/// | ||
/// Example WiX: | ||
/// <Upgrade Id="PUT-GUID-HERE"> | ||
/// <UpgradeVersion Minimum="0.0.0.0" IncludeMinimum="yes" OnlyDetect="yes" Maximum="255.255.0.0" IncludeMaximum="no" Property="MUTUALLY_EXCLUSIVE_PRODUCTS_1" /> | ||
/// </Upgrade> | ||
/// <Condition Message="This product cannot be installed at the same time as [ProductName]. Please uninstall [ProductName] before continuing.">NOT MUTUALLY_EXCLUSIVE_PRODUCTS_1</Condition> | ||
/// <Property Id="MUTUALLY_EXCLUSIVE_PRODUCTS_1" Secure="yes" /> | ||
/// </remarks> | ||
public void Process(ProcessingContext context) | ||
{ | ||
// Append a unique number to the property name. | ||
// | ||
// Windows Installer appends each product code found to the property | ||
// so we could use a single property for all mutually exclusive products, | ||
// but using a unique property for each product lets us include the product name | ||
// in the condition message, which makes the message more user-friendly. | ||
// https://learn.microsoft.com/en-us/windows/win32/msi/upgrade-table | ||
usageCounter++; | ||
var propertyName = $"MUTUALLY_EXCLUSIVE_PRODUCTS_{usageCounter}"; | ||
|
||
var upgradeElement = new XElement("Upgrade"); | ||
upgradeElement.SetAttributeValue("Id", UpgradeCode); | ||
|
||
var upgradeVersionElement = new XElement("UpgradeVersion", | ||
new XAttribute("Minimum", "0.0.0.0"), | ||
new XAttribute("IncludeMinimum", "yes"), | ||
new XAttribute("OnlyDetect", "yes"), | ||
// 255 is the maximum | ||
// https://learn.microsoft.com/en-us/windows/win32/msi/productversion | ||
new XAttribute("Maximum", "255.255.0.0"), | ||
new XAttribute("IncludeMaximum", "yes"), | ||
new XAttribute("Property", propertyName) | ||
); | ||
upgradeElement.Add(upgradeVersionElement); | ||
context.XParent.Add(upgradeElement); | ||
|
||
var conditionElement = new XElement("Condition", | ||
new XAttribute("Message", | ||
$"This product cannot be installed at the same time as {ProductName}. Please uninstall {ProductName} before continuing."), | ||
$"NOT {propertyName}"); | ||
context.XParent.Add(conditionElement); | ||
|
||
// The property specified in this column must be a public property and the | ||
// package author must add the property to the SecureCustomProperties property. | ||
// https://learn.microsoft.com/en-us/windows/win32/msi/upgrade-table | ||
var propertyElement = new XElement("Property", | ||
new XAttribute("Id", propertyName), | ||
new XAttribute("Secure", "yes") | ||
); | ||
context.XParent.Add(propertyElement); | ||
} | ||
} | ||
} |