tintoy / dotnet-kube-client

A Kubernetes API client for .NET Standard / .NET Core
MIT License
192 stars 33 forks source link

YAML serialization of Int32OrStringV1 fails on MaxSurge: "25%" with System.InvalidCastException: The value '25%' is not a valid 32-bit integer #97

Closed felixfbecker closed 5 years ago

felixfbecker commented 5 years ago

Taking this object:


class DeploymentV1
{
  Spec = 
    class DeploymentSpecV1
    {
      Paused = 
      Template = 
        class PodTemplateSpecV1
        {
          Metadata = 
            class ObjectMetaV1
            {
              Uid = 
              ClusterName = 
              GenerateName = 
              Name = 
              Namespace = 
              SelfLink = 
              Generation = 
              ResourceVersion = 
              CreationTimestamp = 
              DeletionTimestamp = 
              Annotations = 
                [
                ]

              DeletionGracePeriodSeconds = 
              Finalizers = 
                [
                ]

              Initializers = 
              Labels = 
                [
                  class 0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
                  {
                    Key = app
                    Value = hello-world
                  }
                ]

              OwnerReferences = 
                [
                ]

              ExtensionData = 
                [
                ]

            }
          Spec = 
            class PodSpecV1
            {
              HostIPC = 
              HostPID = 
              Hostname = 
              NodeName = 
              PriorityClassName = 
              SchedulerName = default-scheduler
              ServiceAccountName = 
              ShareProcessNamespace = 
              DnsConfig = 
              HostNetwork = 
              AutomountServiceAccountToken = 
              Subdomain = 
              NodeSelector = 
                [
                ]

              ActiveDeadlineSeconds = 
              Containers = 
                [
                  class ContainerV1
                  {
                    Command = 
                      [
                      ]

                    Image = strm/helloworld-http@sha256:bd44b0ca80c26b5eba984bf498a9c3bab0eb1c59d30d8df3cb2c073937ee4e45
                    Lifecycle = 
                    LivenessProbe = 
                    Name = hello-world
                    ReadinessProbe = 
                    StdinOnce = 
                    TerminationMessagePath = /dev/termination-log
                    EnvFrom = 
                      [
                      ]

                    Stdin = 
                    WorkingDir = 
                    Args = 
                      [
                      ]

                    Ports = 
                      [
                        KubeClient.Models.ContainerPortV1
                      ]

                    Resources = KubeClient.Models.ResourceRequirementsV1
                    VolumeDevices = 
                      [
                      ]

                    VolumeMounts = 
                      [
                      ]

                    SecurityContext = 
                    Env = 
                      [
                      ]

                    ImagePullPolicy = IfNotPresent
                    TerminationMessagePolicy = File
                    Tty = 
                  }
                ]

              HostAliases = 
                [
                ]

              ImagePullSecrets = 
                [
                ]

              InitContainers = 
                [
                ]

              ReadinessGates = 
                [
                ]

              TerminationGracePeriodSeconds = 30
              Tolerations = 
                [
                ]

              Volumes = 
                [
                ]

              SecurityContext = 
                class PodSecurityContextV1
                {
                  FsGroup = 
                  RunAsGroup = 
                  RunAsUser = 
                  SeLinuxOptions = 
                  SupplementalGroups = 
                    [
                    ]

                  Sysctls = 
                    [
                    ]

                  RunAsNonRoot = 
                }
              ServiceAccount = 
              Affinity = 
              DnsPolicy = ClusterFirst
              Priority = 
              RestartPolicy = Always
            }
        }
      Selector = 
        class LabelSelectorV1
        {
          MatchExpressions = 
            [
            ]

          MatchLabels = 
            [
              class 0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
              {
                Key = app
                Value = hello-world
              }
            ]

        }
      MinReadySeconds = 
      ProgressDeadlineSeconds = 600
      Replicas = 2
      RevisionHistoryLimit = 10
      Strategy = 
        class DeploymentStrategyV1
        {
          RollingUpdate = 
            class RollingUpdateDeploymentV1
            {
              MaxSurge = 
                class Int32OrStringV1
                {
                  Int32Value = 
                  StringValue = 25%
                  IsInt32 = False
                  IsString = True
                }
              MaxUnavailable = 
                class Int32OrStringV1
                {
                  Int32Value = 
                  StringValue = 25%
                  IsInt32 = False
                  IsString = True
                }
            }
          Type = RollingUpdate
        }
    }
  Status = 
    class DeploymentStatusV1
    {
      ObservedGeneration = 
      AvailableReplicas = 
      Conditions = 
        [
        ]

      ReadyReplicas = 
      Replicas = 
      UnavailableReplicas = 
      UpdatedReplicas = 
      CollisionCount = 
    }
  Metadata = 
    class ObjectMetaV1
    {
      Uid = 9e7d1644-90d8-4d9f-a6eb-24d2ec45306c
      ClusterName = 
      GenerateName = 
      Name = hello-world
      Namespace = pskubectltest
      SelfLink = /apis/apps/v1/namespaces/pskubectltest/deployments/hello-world
      Generation = 1
      ResourceVersion = 359273
      CreationTimestamp = 
        class DateTime
        {
          Date = 
            class DateTime
            {
              Date = 
                class DateTime
                {
                  Date = 
                    class DateTime
                    {
                      Date = 9/20/19 12:00:00 AM
                      Day = 20
                      DayOfWeek = Friday
                      DayOfYear = 263
                      Hour = 0
                      Kind = Utc
                      Millisecond = 0
                      Minute = 0
                      Month = 9
                      Second = 0
                      Ticks = 637045344000000000
                      TimeOfDay = 00:00:00
                      Year = 2019
                      DateTime = Friday, September 20, 2019 12:00:00 AM
                    }
                  Day = 20
                  DayOfWeek = Friday
                  DayOfYear = 263
                  Hour = 0
                  Kind = Utc
                  Millisecond = 0
                  Minute = 0
                  Month = 9
                  Second = 0
                  Ticks = 637045344000000000
                  TimeOfDay = 
                    class TimeSpan
                    {
                      Ticks = 0
                      Days = 0
                      Hours = 0
                      Milliseconds = 0
                      Minutes = 0
                      Seconds = 0
                      TotalDays = 0
                      TotalHours = 0
                      TotalMilliseconds = 0
                      TotalMinutes = 0
                      TotalSeconds = 0
                    }
                  Year = 2019
                  DateTime = Friday, September 20, 2019 12:00:00 AM
                }
              Day = 20
              DayOfWeek = Friday
              DayOfYear = 263
              Hour = 0
              Kind = Utc
              Millisecond = 0
              Minute = 0
              Month = 9
              Second = 0
              Ticks = 637045344000000000
              TimeOfDay = 
                class TimeSpan
                {
                  Ticks = 0
                  Days = 0
                  Hours = 0
                  Milliseconds = 0
                  Minutes = 0
                  Seconds = 0
                  TotalDays = 0
                  TotalHours = 0
                  TotalMilliseconds = 0
                  TotalMinutes = 0
                  TotalSeconds = 0
                }
              Year = 2019
              DateTime = Friday, September 20, 2019 12:00:00 AM
            }
          Day = 20
          DayOfWeek = Friday
          DayOfYear = 263
          Hour = 12
          Kind = Utc
          Millisecond = 0
          Minute = 18
          Month = 9
          Second = 30
          Ticks = 637045787100000000
          TimeOfDay = 
            class TimeSpan
            {
              Ticks = 443100000000
              Days = 0
              Hours = 12
              Milliseconds = 0
              Minutes = 18
              Seconds = 30
              TotalDays = 0.512847222222222
              TotalHours = 12.3083333333333
              TotalMilliseconds = 44310000
              TotalMinutes = 738.5
              TotalSeconds = 44310
            }
          Year = 2019
          DateTime = Friday, September 20, 2019 12:18:30 PM
        }
      DeletionTimestamp = 
      Annotations = 
        [
          class 0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
          {
            Key = hello
            Value = world
          }
        ]

      DeletionGracePeriodSeconds = 
      Finalizers = 
        [
        ]

      Initializers = 
      Labels = 
        [
        ]

      OwnerReferences = 
        [
        ]

      ExtensionData = 
        [
          class 0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed]]
          {
            Key = managedFields
            Value = 
              [

                  [
                    {}
                    {}
                    {}
                    {}
                    …
                  ]

                  [
                    {}
                    {}
                    {}
                    {}
                    …
                  ]

              ]

          }
        ]

    }
  Kind = Deployment
  ApiVersion = apps/v1
  Name = hello-world
  Age = 
    class TimeSpan
    {
      Ticks = 72002853550
      Days = 0
      Hours = 2
      Milliseconds = 285
      Minutes = 0
      Seconds = 0
      TotalDays = 0.0833366360532407
      TotalHours = 2.00007926527778
      TotalMilliseconds = 7200285.355
      TotalMinutes = 120.004755916667
      TotalSeconds = 7200.285355
    }
  Namespace = pskubectltest
  Desired = 2
  Current = 
  Updated = 
  Available = 
}

And serializing it with YamlDotNet (not the functions provided by this repo) fails with:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: The value '25%' is not a valid 32-bit integer.
   at KubeClient.Models.Int32OrStringV1.get_Int32Value()
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at YamlDotNet.Serialization.TypeInspectors.ReadablePropertiesTypeInspector.ReflectionPropertyDescriptor.Read(Object target)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.TraverseProperties[TContext](IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.Traverse[TContext](Object name, IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.TraverseProperties[TContext](IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.Traverse[TContext](Object name, IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.TraverseProperties[TContext](IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.Traverse[TContext](Object name, IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.TraverseProperties[TContext](IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.Traverse[TContext](Object name, IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.TraverseProperties[TContext](IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.Traverse[TContext](Object name, IObjectDescriptor value, IObjectGraphVisitor`1 visitor, TContext context, Stack`1 path)
   at YamlDotNet.Serialization.ObjectGraphTraversalStrategies.FullObjectGraphTraversalStrategy.YamlDotNet.Serialization.IObjectGraphTraversalStrategy.Traverse[TContext](IObjectDescriptor graph, IObjectGraphVisitor`1 visitor, TContext context)
   at YamlDotNet.Serialization.SerializerBuilder.ValueSerializer.SerializeValue(IEmitter emitter, Object value, Type type)
   at YamlDotNet.Serialization.Serializer.EmitDocument(IEmitter emitter, Object graph, Type type)
   at YamlDotNet.Serialization.Serializer.Serialize(IEmitter emitter, Object graph)
   at YamlDotNet.Serialization.Serializer.Serialize(TextWriter writer, Object graph)
   at YamlDotNet.Serialization.Serializer.Serialize(Object graph)
   at Kubectl.KubeYamlSerializer.Serialize(Object obj) in /Users/felix/git/PSKubectl/src/KubeYamlSerializer.cs:line 29
   at Kubectl.Cmdlets.ConvertToKubeYamlCmdlet.ProcessRecordAsync(CancellationToken cancellationToken) in /Users/felix/git/PSKubectl/src/Cmdlets/ConvertToKubeYamlCmdlet copy.cs:line 16
   at Kubectl.ThreadAffinitiveSynchronizationContext.RunSynchronized(Func`1 asyncOperation) in /Users/felix/git/PSKubectl/src/ThreadAffinitiveSynchronizationContext.cs:line 151
   at Kubectl.AsyncCmdlet.ProcessRecord() in /Users/felix/git/PSKubectl/src/AsyncCmdlet.cs:line 147
   at System.Management.Automation.Cmdlet.DoProcessRecord()
   at System.Management.Automation.CommandProcessor.ProcessRecord()
tintoy commented 5 years ago

Sounds like we need a custom YAML converter! I’ll look into creating one first thing tomorrow :)

felixfbecker commented 5 years ago

Would be great if it could be specified through an annotation so there is no config needed :)

tintoy commented 5 years ago

Ok - closing this since it's been merged and published. Let me know if you have any problems :)