不可变编程是一种编程思想。简单地说,就是对象的属性只能set一次。
ImmutableEphemeralVolume(immutable secret)
以 kubernetes 最近(2019年年底)的一个 ImmutableEphemeralVolume 为例。
我看了一下源代码,大意就是说,configmap 和 secret 在创建后不可更新。
以 secret 为例,目前(2020-04-16)secret 的定义是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Secret holds secret data of a certain type. The total bytes of the values in
// the Data field must be less than MaxSecretSize bytes.
type Secret struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Immutable, if set to true, ensures that data stored in the Secret cannot
// be updated (only object metadata can be modified).
// If not set to true, the field can be modified at any time.
// Defaulted to nil.
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
// +optional
Immutable *bool `json:"immutable,omitempty" protobuf:"varint,5,opt,name=immutable"`
// Data contains the secret data. Each key must consist of alphanumeric
// characters, '-', '_' or '.'. The serialized form of the secret data is a
// base64 encoded string, representing the arbitrary (possibly non-string)
// data value here. Described in https://tools.ietf.org/html/rfc4648#section-4
// +optional
Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
// stringData allows specifying non-binary secret data in string form.
// It is provided as a write-only convenience method.
// All keys and values are merged into the data field on write, overwriting any existing values.
// It is never output when reading from the API.
// +k8s:conversion-gen=false
// +optional
StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,rep,name=stringData"`
// Used to facilitate programmatic handling of secret data.
// +optional
Type SecretType `json:"type,omitempty" protobuf:"bytes,3,opt,name=type,casttype=SecretType"`
}
其实只看
1
Immutable *bool `json:"immutable,omitempty"`
就可以了。可以看到,这是一个 bool 的指针。因为这个字段目前处于alpha 的阶段,所以用了 omitempty 这个标签忽略掉了。
判断是否已经注入
Secret 有个 String 方法有点意思。
简单地说就是通过反射判断字段是否已经注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (this *Secret) String() string {
......
s := strings.Join([]string{`&Secret{`,
`ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
`Data:` + mapStringForData + `,`,
`Type:` + fmt.Sprintf("%v", this.Type) + `,`,
`StringData:` + mapStringForStringData + `,`,
`Immutable:` + valueToStringGenerated(this.Immutable) + `,`,
`}`,
}, "")
return s
}
valueToStringGenerated 方法展开是这样的:
1
2
3
4
5
6
7
8
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
我简化了一下模型,写了个例子。
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"reflect"
)
type Secret struct {
Immutable *bool `json:"immutable,omitempty"`
}
func main() {
s := Secret{Immutable: &[]bool{true}[0]}
fmt.Println(valueToStringGenerated(s.Immutable)) // *true
s = Secret{}
fmt.Println(valueToStringGenerated(s.Immutable)) // nil
}
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
结论
为 struct 增加一个字段,这个字段是一个指针。
通过反射获取 struct 的成员(比如字段),进而判断是否已经注入。
有些情况(比如string),用私有字段,struct 暴露一个单例模式的 Set 方法也行。我猜是 bool 类型比较特殊,所以 kubernetes 官方才用了 *bool 这个数据结构。
参考链接
Immutable programming is a programming philosophy. Simply put, it means that an object’s properties can only be set once.
ImmutableEphemeralVolume(immutable secret)
Take kubernetes’ recent (end of 2019) ImmutableEphemeralVolume as an example.
I looked at the source code, and the main idea is that configmap and secret cannot be updated after creation.
Taking secret as an example, the current (2020-04-16) definition of secret is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Secret holds secret data of a certain type. The total bytes of the values in
// the Data field must be less than MaxSecretSize bytes.
type Secret struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Immutable, if set to true, ensures that data stored in the Secret cannot
// be updated (only object metadata can be modified).
// If not set to true, the field can be modified at any time.
// Defaulted to nil.
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
// +optional
Immutable *bool `json:"immutable,omitempty" protobuf:"varint,5,opt,name=immutable"`
// Data contains the secret data. Each key must consist of alphanumeric
// characters, '-', '_' or '.'. The serialized form of the secret data is a
// base64 encoded string, representing the arbitrary (possibly non-string)
// data value here. Described in https://tools.ietf.org/html/rfc4648#section-4
// +optional
Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
// stringData allows specifying non-binary secret data in string form.
// It is provided as a write-only convenience method.
// All keys and values are merged into the data field on write, overwriting any existing values.
// It is never output when reading from the API.
// +k8s:conversion-gen=false
// +optional
StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,opt,name=stringData"`
// Used to facilitate programmatic handling of secret data.
// +optional
Type SecretType `json:"type,omitempty" protobuf:"bytes,3,opt,name=type,casttype=SecretType"`
}
Actually, just look at
1
Immutable *bool `json:"immutable,omitempty"`
That’s enough. You can see this is a pointer to bool. Because this field is currently in the alpha stage, the omitempty tag is used to ignore it.
Determining if it has been injected
Secret has a String method that’s interesting.
Simply put, it uses reflection to determine if a field has been injected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (this *Secret) String() string {
......
s := strings.Join([]string{`&Secret{`,
`ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
`Data:` + mapStringForData + `,`,
`Type:` + fmt.Sprintf("%v", this.Type) + `,`,
`StringData:` + mapStringForStringData + `,`,
`Immutable:` + valueToStringGenerated(this.Immutable) + `,`,
`}`,
}, "")
return s
}
The valueToStringGenerated method expands like this:
1
2
3
4
5
6
7
8
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
I simplified the model and wrote an example.
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"reflect"
)
type Secret struct {
Immutable *bool `json:"immutable,omitempty"`
}
func main() {
s := Secret{Immutable: &[]bool{true}[0]}
fmt.Println(valueToStringGenerated(s.Immutable)) // *true
s = Secret{}
fmt.Println(valueToStringGenerated(s.Immutable)) // nil
}
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
Conclusion
Add a field to the struct, this field is a pointer.
Use reflection to get the members of the struct (such as fields), and then determine if they have been injected.
In some cases (like string), using a private field and exposing a singleton-pattern Set method from the struct also works. I guess bool is special, which is why kubernetes officially used the *bool data structure.
References
不変プログラミングはプログラミングの思想です。簡単に言えば、オブジェクトのプロパティは一度だけ設定できるということです。
ImmutableEphemeralVolume(immutable secret)
kubernetesの最近(2019年末)のImmutableEphemeralVolumeを例にします。
ソースコードを見たところ、要するにconfigmapとsecretは作成後に更新できないということです。
secretを例にすると、現在(2020-04-16)のsecretの定義は次のとおりです:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Secret holds secret data of a certain type. The total bytes of the values in
// the Data field must be less than MaxSecretSize bytes.
type Secret struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Immutable, if set to true, ensures that data stored in the Secret cannot
// be updated (only object metadata can be modified).
// If not set to true, the field can be modified at any time.
// Defaulted to nil.
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
// +optional
Immutable *bool `json:"immutable,omitempty" protobuf:"varint,5,opt,name=immutable"`
// Data contains the secret data. Each key must consist of alphanumeric
// characters, '-', '_' or '.'. The serialized form of the secret data is a
// base64 encoded string, representing the arbitrary (possibly non-string)
// data value here. Described in https://tools.ietf.org/html/rfc4648#section-4
// +optional
Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
// stringData allows specifying non-binary secret data in string form.
// It is provided as a write-only convenience method.
// All keys and values are merged into the data field on write, overwriting any existing values.
// It is never output when reading from the API.
// +k8s:conversion-gen=false
// +optional
StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,opt,name=stringData"`
// Used to facilitate programmatic handling of secret data.
// +optional
Type SecretType `json:"type,omitempty" protobuf:"bytes,3,opt,name=type,casttype=SecretType"`
}
実際には
1
Immutable *bool `json:"immutable,omitempty"`
を見れば十分です。これはboolへのポインタです。このフィールドは現在alpha段階にあるため、omitemptyタグを使用して無視されています。
注入されたかどうかの判断
Secretには興味深いStringメソッドがあります。
簡単に言えば、リフレクションを使用してフィールドが注入されたかどうかを判断します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (this *Secret) String() string {
......
s := strings.Join([]string{`&Secret{`,
`ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
`Data:` + mapStringForData + `,`,
`Type:` + fmt.Sprintf("%v", this.Type) + `,`,
`StringData:` + mapStringForStringData + `,`,
`Immutable:` + valueToStringGenerated(this.Immutable) + `,`,
`}`,
}, "")
return s
}
valueToStringGeneratedメソッドは次のように展開されます:
1
2
3
4
5
6
7
8
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
モデルを簡略化して、例を作成しました。
例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"reflect"
)
type Secret struct {
Immutable *bool `json:"immutable,omitempty"`
}
func main() {
s := Secret{Immutable: &[]bool{true}[0]}
fmt.Println(valueToStringGenerated(s.Immutable)) // *true
s = Secret{}
fmt.Println(valueToStringGenerated(s.Immutable)) // nil
}
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
結論
structにフィールドを追加します。このフィールドはポインタです。
リフレクションを使用してstructのメンバー(フィールドなど)を取得し、注入されたかどうかを判断します。
一部のケース(stringなど)では、プライベートフィールドを使用し、structがシングルトンパターンのSetメソッドを公開することも可能です。bool型が特殊であるため、kubernetes公式が*boolデータ構造を使用したのだと思います。
参考リンク
Неизменяемое программирование — это философия программирования. Проще говоря, это означает, что свойства объекта можно установить только один раз.
ImmutableEphemeralVolume(immutable secret)
Возьмём в качестве примера недавний (конец 2019 года) ImmutableEphemeralVolume в kubernetes.
Я посмотрел исходный код, основная идея в том, что configmap и secret нельзя обновить после создания.
В качестве примера возьмём secret, текущее (2020-04-16) определение secret такое:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Secret holds secret data of a certain type. The total bytes of the values in
// the Data field must be less than MaxSecretSize bytes.
type Secret struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Immutable, if set to true, ensures that data stored in the Secret cannot
// be updated (only object metadata can be modified).
// If not set to true, the field can be modified at any time.
// Defaulted to nil.
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
// +optional
Immutable *bool `json:"immutable,omitempty" protobuf:"varint,5,opt,name=immutable"`
// Data contains the secret data. Each key must consist of alphanumeric
// characters, '-', '_' or '.'. The serialized form of the secret data is a
// base64 encoded string, representing the arbitrary (possibly non-string)
// data value here. Described in https://tools.ietf.org/html/rfc4648#section-4
// +optional
Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
// stringData allows specifying non-binary secret data in string form.
// It is provided as a write-only convenience method.
// All keys and values are merged into the data field on write, overwriting any existing values.
// It is never output when reading from the API.
// +k8s:conversion-gen=false
// +optional
StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,opt,name=stringData"`
// Used to facilitate programmatic handling of secret data.
// +optional
Type SecretType `json:"type,omitempty" protobuf:"bytes,3,opt,name=type,casttype=SecretType"`
}
На самом деле достаточно посмотреть на
1
Immutable *bool `json:"immutable,omitempty"`
Видно, что это указатель на bool. Поскольку это поле находится на стадии alpha, используется тег omitempty для его игнорирования.
Определение, было ли оно внедрено
У Secret есть интересный метод String.
Проще говоря, он использует рефлексию для определения, было ли поле внедрено.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (this *Secret) String() string {
......
s := strings.Join([]string{`&Secret{`,
`ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
`Data:` + mapStringForData + `,`,
`Type:` + fmt.Sprintf("%v", this.Type) + `,`,
`StringData:` + mapStringForStringData + `,`,
`Immutable:` + valueToStringGenerated(this.Immutable) + `,`,
`}`,
}, "")
return s
}
Метод valueToStringGenerated разворачивается так:
1
2
3
4
5
6
7
8
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
Я упростил модель и написал пример.
Пример
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"reflect"
)
type Secret struct {
Immutable *bool `json:"immutable,omitempty"`
}
func main() {
s := Secret{Immutable: &[]bool{true}[0]}
fmt.Println(valueToStringGenerated(s.Immutable)) // *true
s = Secret{}
fmt.Println(valueToStringGenerated(s.Immutable)) // nil
}
func valueToStringGenerated(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
Заключение
Добавить поле в struct, это поле — указатель.
Использовать рефлексию для получения членов struct (таких как поля), а затем определить, были ли они внедрены.
В некоторых случаях (например, string) использование приватного поля и предоставление struct метода Set в паттерне singleton также работает. Я думаю, bool особенный, поэтому kubernetes официально использовал структуру данных *bool.
Ссылки
💬 讨论 / Discussion
对这篇文章有想法?欢迎在 GitHub 上发起讨论。
Have thoughts on this post? Start a discussion on GitHub.