Search code examples
gokuberneteskubernetes-pod

Go struct: multiple nested fields but only one field can be specified


I wanted to know how to implement inheritance in Go. After reading I understood that I must think about struct embedding. As I am a Kubernetes developer, I jump into the Kubernetes source code and start reading the PodSpec in which the volumes field is closer to what I am looking for.

When I thought I was starting to understand, something intrigued me. By reading the snippet below, before the type VolumeSource struct you can read Only one of its members may be specified

type Volume struct {
    // Volume's name.
    // Must be a DNS_LABEL and unique within the pod.
    // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
    Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
    // VolumeSource represents the location and type of the mounted volume.
    // If not specified, the Volume is implied to be an EmptyDir.
    // This implied behavior is deprecated and will be removed in a future version.
    VolumeSource `json:",inline" protobuf:"bytes,2,opt,name=volumeSource"`
}

// Represents the source of a volume to mount.
// Only one of its members may be specified.
type VolumeSource struct {
    // HostPath represents a pre-existing file or directory on the host
    // machine that is directly exposed to the container. This is generally
    // used for system agents or other privileged things that are allowed
    // to see the host machine. Most containers will NOT need this.
    // More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath
    // ---
    // TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not
    // mount host directories as read/write.
    // +optional
    HostPath *HostPathVolumeSource `json:"hostPath,omitempty" protobuf:"bytes,1,opt,name=hostPath"`
    // EmptyDir represents a temporary directory that shares a pod's lifetime.
    // More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
    // +optional
    EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty" protobuf:"bytes,2,opt,name=emptyDir"`
    // GCEPersistentDisk represents a GCE Disk resource that is attached to a
    // kubelet's host machine and then exposed to the pod.
    // More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk
    // +optional
    GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"gcePersistentDisk,omitempty" protobuf:"bytes,3,opt,name=gcePersistentDisk"`
    // AWSElasticBlockStore represents an AWS Disk resource that is attached to a
    // kubelet's host machine and then exposed to the pod.
    // More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore
    // +optional
    AWSElasticBlockStore *AWSElasticBlockStoreVolumeSource `json:"awsElasticBlockStore,omitempty" protobuf:"bytes,4,opt,name=awsElasticBlockStore"`
    // GitRepo represents a git repository at a particular revision.
    // DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an
    // EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir
    // into the Pod's container.
    // +optional
    ....

How and where is this constraint satisfied? Is it in the struct definition as it's written or programmatically in the source code in another .go file? Because I looked for it in the source code where it could be programmatically verified, but I didn't see.

Please, can you help me to understand?


Solution

  • there's no such thing to limit a structs field values in the language spec. However, I have a good feeling that since all fields are pointers, a method of volume source checks for all fields and expects only one field to be non-nil.

    Maybe something like this, I'm not sure of the usage of reflect though

    func (v VolumeSource) CheckFields() error {
        s := reflect.TypeOf(v)
        counter := 0
        for i:= s.NumField() - 1; i >= 0 ; i-- {
            n := s.Field(i).Name()
            e := reflect.ValueOf(v).Field(i)
            if !e.IsValid(){
                continue
            }
            if e != nil {
                counter++
            }
            if counter > 1{
                return errors.New("more than 1 field initialized")
            }
    
        }
    }