public static void MergeImageArtifactDetails(ImageArtifactDetails src, ImageArtifactDetails target, ImageInfoMergeOptions options = null) { if (options == null) { options = new ImageInfoMergeOptions(); } MergeData(src, target, options); }
private static void MergeData(object srcObj, object targetObj, ImageInfoMergeOptions options) { if (srcObj.GetType() != targetObj.GetType()) { throw new ArgumentException("Object types don't match.", nameof(targetObj)); } foreach (PropertyInfo property in srcObj.GetType().GetProperties()) { if (property.PropertyType == typeof(string)) { property.SetValue(targetObj, property.GetValue(srcObj)); } else if (typeof(IDictionary).IsAssignableFrom(property.PropertyType)) { MergeDictionaries(property, srcObj, targetObj, options); } else if (typeof(IList <string>).IsAssignableFrom(property.PropertyType)) { if (srcObj is ImageData && property.Name == nameof(ImageData.SimpleTags) && options.ReplaceTags) { // SimpleTags can be merged or replaced depending on the scenario. // When merging multiple image info files together into a single file, the tags should be // merged to account for cases where tags for a given image are spread across multiple // image info files. But when publishing an image info file it the source tags should replace // the destination tags. Any of the image's tags contained in the target should be considered // obsolete and should be replaced by the source. This accounts for the scenario where shared // tags are moved from one image to another. If we had merged instead of replaced, then the // shared tag would not have been removed from the original image in the image info in such // a scenario. // See: // - https://github.com/dotnet/docker-tools/pull/269 // - https://github.com/dotnet/docker-tools/issues/289 ReplaceValue(property, srcObj, targetObj); } else { MergeLists(property, srcObj, targetObj); } } else { throw new NotSupportedException($"Unsupported model property type: '{property.PropertyType.FullName}'"); } } }
private static void MergeDictionaries(PropertyInfo property, object srcObj, object targetObj, ImageInfoMergeOptions options) { IDictionary srcDict = (IDictionary)property.GetValue(srcObj); if (srcDict == null) { return; } IDictionary targetDict = (IDictionary)property.GetValue(targetObj); if (srcDict.Cast <object>().Any()) { if (targetDict != null) { foreach (dynamic kvp in srcDict) { if (targetDict.Contains(kvp.Key)) { object newValue = kvp.Value; if (newValue is string) { targetDict[kvp.Key] = newValue; } else { MergeData(kvp.Value, targetDict[kvp.Key], options); } } else { targetDict[kvp.Key] = kvp.Value; } } } else { property.SetValue(targetObj, srcDict); } } }
public static void MergeRepos(RepoData[] srcRepos, List <RepoData> targetRepos, ImageInfoMergeOptions options = null) { if (options == null) { options = new ImageInfoMergeOptions(); } foreach (RepoData srcRepo in srcRepos) { RepoData targetRepo = targetRepos.FirstOrDefault(r => r.Repo == srcRepo.Repo); if (targetRepo == null) { targetRepos.Add(srcRepo); } else { MergeData(srcRepo, targetRepo, options); } } }
private static void MergeData(object srcObj, object targetObj, ImageInfoMergeOptions options) { if (!((srcObj is null && targetObj is null) || (!(srcObj is null) && !(targetObj is null)))) { throw new InvalidOperationException("The src and target objects must either be both null or both non-null."); } if (srcObj is null) { return; } if (srcObj.GetType() != targetObj.GetType()) { throw new ArgumentException("Object types don't match.", nameof(targetObj)); } IEnumerable <PropertyInfo> properties = srcObj.GetType().GetProperties() .Where(property => property.GetCustomAttribute <JsonIgnoreAttribute>() == null); foreach (PropertyInfo property in properties) { if (property.PropertyType == typeof(string) || property.PropertyType == typeof(DateTime)) { property.SetValue(targetObj, property.GetValue(srcObj)); } else if (typeof(IDictionary).IsAssignableFrom(property.PropertyType)) { MergeDictionaries(property, srcObj, targetObj, options); } else if (typeof(IList <string>).IsAssignableFrom(property.PropertyType)) { if (options.ReplaceTags && ((srcObj is PlatformData && property.Name == nameof(PlatformData.SimpleTags)) || (srcObj is ManifestData && property.Name == nameof(ManifestData.SharedTags)))) { // Tags can be merged or replaced depending on the scenario. // When merging multiple image info files together into a single file, the tags should be // merged to account for cases where tags for a given image are spread across multiple // image info files. But when publishing an image info file the source tags should replace // the destination tags. Any of the image's tags contained in the target should be considered // obsolete and should be replaced by the source. This accounts for the scenario where shared // tags are moved from one image to another. If we had merged instead of replaced, then the // shared tag would not have been removed from the original image in the image info in such // a scenario. // See: // - https://github.com/dotnet/docker-tools/pull/269 // - https://github.com/dotnet/docker-tools/issues/289 ReplaceValue(property, srcObj, targetObj); } else { MergeStringLists(property, srcObj, targetObj); } } else if (typeof(IList <ImageData>).IsAssignableFrom(property.PropertyType)) { MergeLists <ImageData>(property, srcObj, targetObj, options); } else if (typeof(IList <PlatformData>).IsAssignableFrom(property.PropertyType)) { MergeLists <PlatformData>(property, srcObj, targetObj, options); } else if (typeof(IList <RepoData>).IsAssignableFrom(property.PropertyType)) { MergeLists <RepoData>(property, srcObj, targetObj, options); } else if (typeof(ManifestData).IsAssignableFrom(property.PropertyType)) { MergeData(property.GetValue(srcObj), property.GetValue(targetObj), options); } else { throw new NotSupportedException($"Unsupported model property type: '{property.PropertyType.FullName}'"); } } }
private static void MergeLists <T>(PropertyInfo property, object srcObj, object targetObj, ImageInfoMergeOptions options) where T : IComparable <T> { IList <T> srcList = (IList <T>)property.GetValue(srcObj); if (srcList == null) { return; } IList <T> targetList = (IList <T>)property.GetValue(targetObj); if (srcList.Any()) { if (targetList?.Any() == true) { foreach (T srcItem in srcList) { T matchingTargetItem = targetList .FirstOrDefault(targetItem => srcItem.CompareTo(targetItem) == 0); if (matchingTargetItem != null) { MergeData(srcItem, matchingTargetItem, options); } else { targetList.Add(srcItem); } } } else { targetList = srcList; } List <T> sortedList = targetList.ToList(); sortedList.Sort(); property.SetValue(targetObj, sortedList); } }
private static void MergePropertyData(object srcObj, object targetObj, PropertyInfo property, ImageInfoMergeOptions options) { object srcResult = property.GetValue(srcObj); object targetResult = property.GetValue(targetObj); if (srcResult is null && targetResult != null) { property.SetValue(targetObj, null); }