@@ -19,6 +19,7 @@ use super::{KubeconfigError, LoadDataError};
19
19
/// [`Config`][crate::Config] is the __intended__ developer interface to help create a [`Client`][crate::Client],
20
20
/// and this will handle the difference between in-cluster deployment and local development.
21
21
#[ derive( Clone , Debug , Serialize , Deserialize , Default ) ]
22
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
22
23
pub struct Kubeconfig {
23
24
/// General information to be use for cli interactions
24
25
#[ serde( skip_serializing_if = "Option::is_none" ) ]
@@ -48,6 +49,7 @@ pub struct Kubeconfig {
48
49
49
50
/// Preferences stores extensions for cli.
50
51
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
52
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
51
53
pub struct Preferences {
52
54
#[ serde( skip_serializing_if = "Option::is_none" ) ]
53
55
pub colors : Option < bool > ,
@@ -57,20 +59,23 @@ pub struct Preferences {
57
59
58
60
/// NamedExtention associates name with extension.
59
61
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
62
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
60
63
pub struct NamedExtension {
61
64
pub name : String ,
62
65
pub extension : serde_json:: Value ,
63
66
}
64
67
65
68
/// NamedCluster associates name with cluster.
66
69
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
70
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
67
71
pub struct NamedCluster {
68
72
pub name : String ,
69
73
pub cluster : Cluster ,
70
74
}
71
75
72
76
/// Cluster stores information to connect Kubernetes cluster.
73
77
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
78
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
74
79
pub struct Cluster {
75
80
/// The address of the kubernetes cluster (https://hostname:port).
76
81
pub server : String ,
@@ -96,6 +101,7 @@ pub struct Cluster {
96
101
97
102
/// NamedAuthInfo associates name with authentication.
98
103
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
104
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
99
105
pub struct NamedAuthInfo {
100
106
pub name : String ,
101
107
#[ serde( rename = "user" ) ]
@@ -104,6 +110,7 @@ pub struct NamedAuthInfo {
104
110
105
111
/// AuthInfo stores information to tell cluster who you are.
106
112
#[ derive( Clone , Debug , Serialize , Deserialize , Default ) ]
113
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
107
114
pub struct AuthInfo {
108
115
/// The username for basic authentication to the kubernetes cluster.
109
116
#[ serde( skip_serializing_if = "Option::is_none" ) ]
@@ -159,13 +166,15 @@ pub struct AuthInfo {
159
166
160
167
/// AuthProviderConfig stores auth for specified cloud provider.
161
168
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
169
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
162
170
pub struct AuthProviderConfig {
163
171
pub name : String ,
164
172
pub config : HashMap < String , String > ,
165
173
}
166
174
167
175
/// ExecConfig stores credential-plugin configuration.
168
176
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
177
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
169
178
pub struct ExecConfig {
170
179
/// Preferred input version of the ExecInfo.
171
180
///
@@ -187,13 +196,15 @@ pub struct ExecConfig {
187
196
188
197
/// NamedContext associates name with context.
189
198
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
199
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
190
200
pub struct NamedContext {
191
201
pub name : String ,
192
202
pub context : Context ,
193
203
}
194
204
195
205
/// Context stores tuple of cluster and user information.
196
206
#[ derive( Clone , Debug , Serialize , Deserialize ) ]
207
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
197
208
pub struct Context {
198
209
/// Name of the cluster for this context
199
210
pub cluster : String ,
@@ -215,17 +226,10 @@ impl Kubeconfig {
215
226
pub fn read_from < P : AsRef < Path > > ( path : P ) -> Result < Kubeconfig , KubeconfigError > {
216
227
let data = fs:: read_to_string ( & path)
217
228
. map_err ( |source| KubeconfigError :: ReadConfig ( source, path. as_ref ( ) . into ( ) ) ) ?;
218
- // support multiple documents
219
- let mut documents: Vec < Kubeconfig > = vec ! [ ] ;
220
- for doc in serde_yaml:: Deserializer :: from_str ( & data) {
221
- let value = serde_yaml:: Value :: deserialize ( doc) . map_err ( KubeconfigError :: Parse ) ?;
222
- let kconf = serde_yaml:: from_value ( value) . map_err ( KubeconfigError :: InvalidStructure ) ?;
223
- documents. push ( kconf)
224
- }
225
229
226
230
// Remap all files we read to absolute paths.
227
231
let mut merged_docs = None ;
228
- for mut config in documents {
232
+ for mut config in kubeconfig_from_yaml ( & data ) ? {
229
233
if let Some ( dir) = path. as_ref ( ) . parent ( ) {
230
234
for named in config. clusters . iter_mut ( ) {
231
235
if let Some ( path) = & named. cluster . certificate_authority {
@@ -262,6 +266,16 @@ impl Kubeconfig {
262
266
Ok ( merged_docs. unwrap_or_default ( ) )
263
267
}
264
268
269
+ /// Read a Config from an arbitrary YAML string
270
+ ///
271
+ /// This is preferable to using serde_yaml::from_str() because it will correctly
272
+ /// parse multi-document YAML text and merge them into a single `Kubeconfig`
273
+ pub fn from_yaml ( text : & str ) -> Result < Kubeconfig , KubeconfigError > {
274
+ kubeconfig_from_yaml ( text) ?
275
+ . into_iter ( )
276
+ . try_fold ( Kubeconfig :: default ( ) , Kubeconfig :: merge)
277
+ }
278
+
265
279
/// Read a Config from `KUBECONFIG` or the the default location.
266
280
pub fn read ( ) -> Result < Kubeconfig , KubeconfigError > {
267
281
match Self :: from_env ( ) ? {
@@ -328,6 +342,16 @@ impl Kubeconfig {
328
342
}
329
343
}
330
344
345
+ fn kubeconfig_from_yaml ( text : & str ) -> Result < Vec < Kubeconfig > , KubeconfigError > {
346
+ let mut documents = vec ! [ ] ;
347
+ for doc in serde_yaml:: Deserializer :: from_str ( text) {
348
+ let value = serde_yaml:: Value :: deserialize ( doc) . map_err ( KubeconfigError :: Parse ) ?;
349
+ let kubeconfig = serde_yaml:: from_value ( value) . map_err ( KubeconfigError :: InvalidStructure ) ?;
350
+ documents. push ( kubeconfig) ;
351
+ }
352
+ Ok ( documents)
353
+ }
354
+
331
355
#[ allow( clippy:: redundant_closure) ]
332
356
fn append_new_named < T , F > ( base : & mut Vec < T > , next : Vec < T > , f : F )
333
357
where
@@ -519,7 +543,7 @@ users:
519
543
client-certificate: /home/kevin/.minikube/profiles/minikube/client.crt
520
544
client-key: /home/kevin/.minikube/profiles/minikube/client.key" ;
521
545
522
- let config: Kubeconfig = serde_yaml :: from_str ( config_yaml) . unwrap ( ) ;
546
+ let config = Kubeconfig :: from_yaml ( config_yaml) . unwrap ( ) ;
523
547
524
548
assert_eq ! ( config. clusters[ 0 ] . name, "eks" ) ;
525
549
assert_eq ! ( config. clusters[ 1 ] . name, "minikube" ) ;
@@ -574,14 +598,19 @@ users:
574
598
client-certificate-data: aGVsbG8K
575
599
client-key-data: aGVsbG8K
576
600
"# ;
577
- let file = tempfile:: NamedTempFile :: new ( ) . expect ( "create config tempfile" ) ;
578
- fs:: write ( file. path ( ) , config_yaml) . unwrap ( ) ;
579
- let cfg = Kubeconfig :: read_from ( file. path ( ) ) ?;
601
+ let cfg = Kubeconfig :: from_yaml ( config_yaml) ?;
580
602
581
603
// Ensure we have data from both documents:
582
604
assert_eq ! ( cfg. clusters[ 0 ] . name, "k3d-promstack" ) ;
583
605
assert_eq ! ( cfg. clusters[ 1 ] . name, "k3d-k3s-default" ) ;
584
606
585
607
Ok ( ( ) )
586
608
}
609
+
610
+ #[ test]
611
+ fn kubeconfig_from_empty_string ( ) {
612
+ let cfg = Kubeconfig :: from_yaml ( "" ) . unwrap ( ) ;
613
+
614
+ assert_eq ! ( cfg, Kubeconfig :: default ( ) ) ;
615
+ }
587
616
}
0 commit comments