1
+ use std:: env;
2
+ use std:: io:: Write ;
3
+ use std:: fs:: OpenOptions ;
4
+ use std:: net:: IpAddr ;
5
+ use std:: os:: unix:: fs:: OpenOptionsExt ;
6
+ use std:: process:: { Command , Stdio } ;
7
+
8
+ use serde:: Deserialize ;
1
9
use log:: debug;
10
+ use tera:: Context ;
11
+ use tera:: Tera ;
2
12
3
- use crate :: K8Config ;
13
+ use crate :: { K8Config , ConfigError } ;
4
14
5
15
fn load_cert_auth ( ) -> String {
6
16
let k8_config = K8Config :: load ( ) . expect ( "loading" ) ;
@@ -25,71 +35,234 @@ fn load_cert_auth() -> String {
25
35
. to_string ( )
26
36
}
27
37
28
- pub struct Option {
29
- pub ctx_name : String ,
38
+ /// Configuration options to wire up Fluvio on Minikube
39
+ pub struct MinikubeContext {
40
+ name : String ,
41
+ profile : MinikubeProfile ,
30
42
}
31
43
32
- impl Default for Option {
33
- fn default ( ) -> Self {
34
- Option {
35
- ctx_name : "flvkube" . to_owned ( ) ,
44
+ impl MinikubeContext {
45
+ /// Attempts to derive a `MinikubeContext` from the system
46
+ ///
47
+ /// This requires the presence of the `minikube` executable,
48
+ /// which will tell us the current IP and port that minikube
49
+ /// is running on.
50
+ ///
51
+ /// # Example
52
+ ///
53
+ /// ```no_run
54
+ /// use k8_config::context::MinikubeContext;
55
+ /// let context = MinikubeContext::try_from_system().unwrap();
56
+ /// ```
57
+ pub fn try_from_system ( ) -> Result < Self , ConfigError > {
58
+ Ok ( Self {
59
+ name : "flvkube" . to_string ( ) ,
60
+ profile : MinikubeProfile :: load ( ) ?,
61
+ } )
62
+ }
63
+
64
+ /// Sets the name of the context
65
+ ///
66
+ /// # Example
67
+ ///
68
+ /// ```no_run
69
+ /// use k8_config::context::MinikubeContext;
70
+ /// let context = MinikubeContext::try_from_system().unwrap()
71
+ /// .with_name("my-minikube");
72
+ /// ```
73
+ pub fn with_name < S : Into < String > > ( mut self , name : S ) -> Self {
74
+ self . name = name. into ( ) ;
75
+ self
76
+ }
77
+
78
+ /// Saves the Minikube context for kubectl and updates the minikube IP
79
+ ///
80
+ /// # Example
81
+ ///
82
+ /// ```no_run
83
+ /// use k8_config::context::MinikubeContext;
84
+ /// let context = MinikubeContext::try_from_system().unwrap();
85
+ /// context.save().unwrap();
86
+ /// ```
87
+ pub fn save ( & self ) -> Result < ( ) , ConfigError > {
88
+ // Check if the detected minikube IP matches the /etc/hosts entry
89
+ if !self . profile . matches_hostfile ( ) ? {
90
+ // If the /etc/hosts file is not up to date, update it
91
+ debug ! ( "hosts file is outdated: updating" ) ;
92
+ self . update_hosts ( ) ?;
36
93
}
94
+ self . update_kubectl_context ( ) ?;
95
+ Ok ( ( ) )
37
96
}
38
- }
39
97
40
- /// create kube context that copy current cluster configuration
41
- pub fn create_dns_context ( option : Option ) {
42
- const TEMPLATE : & ' static str = r#"
98
+ /// Updates the `kubectl` context to use the current settings
99
+ fn update_kubectl_context ( & self ) -> Result < ( ) , ConfigError > {
100
+ Command :: new ( "kubectl" )
101
+ . args ( & [ "config" , "set-cluster" , & self . name ] )
102
+ . arg ( & format ! ( "--server=https://minikubeCA:{}" , self . profile. port( ) ) )
103
+ . arg ( & format ! ( "--certificate-authority={}" , load_cert_auth( ) ) )
104
+ . stdout ( Stdio :: inherit ( ) )
105
+ . stderr ( Stdio :: inherit ( ) )
106
+ . status ( ) ?;
107
+
108
+ Command :: new ( "kubectl" )
109
+ . args ( & [ "config" , "set-context" , & self . name ] )
110
+ . arg ( "--user=minikube" )
111
+ . arg ( & format ! ( "--cluster={}" , & self . name) )
112
+ . stdout ( Stdio :: inherit ( ) )
113
+ . stderr ( Stdio :: inherit ( ) )
114
+ . status ( ) ?;
115
+
116
+ Command :: new ( "kubectl" )
117
+ . args ( & [ "config" , "use-context" , & self . name ] )
118
+ . stdout ( Stdio :: inherit ( ) )
119
+ . stderr ( Stdio :: inherit ( ) )
120
+ . status ( ) ?;
121
+
122
+ Ok ( ( ) )
123
+ }
124
+
125
+ /// Updates the `/etc/hosts` file by rewriting the line with `minikubeCA`
126
+ fn update_hosts ( & self ) -> Result < ( ) , ConfigError > {
127
+ const TEMPLATE : & ' static str = r#"
43
128
#!/bin/bash
44
- export IP=$(minikube ip)
45
- sudo sed -i '' '/minikubeCA/d' /etc/hosts
129
+ # Get IP from context, if available
130
+ export IP={{ ip }}
131
+ # If there is no IP in context, use "minikube ip"
132
+ export IP="${IP:-$(minikube ip)}"
133
+ sudo sed -i'' -e '/minikubeCA/d' /etc/hosts
46
134
echo "$IP minikubeCA" | sudo tee -a /etc/hosts
47
- cd ~
48
- kubectl config set-cluster {{ name }} --server=https://minikubeCA:8443 --certificate-authority={{ ca }}
49
- kubectl config set-context {{ name }} --user=minikube --cluster={{ name }}
50
- kubectl config use-context {{ name }}
51
135
"# ;
52
136
53
- use std:: env;
54
- use std:: fs:: OpenOptions ;
55
- use std:: io;
56
- use std:: io:: Write ;
57
- use std:: os:: unix:: fs:: OpenOptionsExt ;
58
- use std:: process:: Command ;
137
+ let mut tera = Tera :: default ( ) ;
138
+ tera. add_raw_template ( "cube.sh" , TEMPLATE )
139
+ . expect ( "string compilation" ) ;
140
+
141
+ let mut context = Context :: new ( ) ;
142
+ context. insert ( "ip" , & self . profile . ip ( ) ) ;
143
+
144
+ let render = tera. render ( "cube.sh" , & context) . expect ( "rendering" ) ;
145
+ let tmp_file = env:: temp_dir ( ) . join ( "flv_minikube.sh" ) ;
146
+
147
+ let mut file = OpenOptions :: new ( )
148
+ . create ( true )
149
+ . write ( true )
150
+ . truncate ( true )
151
+ . mode ( 0o755 )
152
+ . open ( tmp_file. clone ( ) )
153
+ . expect ( "temp script can't be created" ) ;
154
+
155
+ file. write_all ( render. as_bytes ( ) )
156
+ . expect ( "file write failed" ) ;
59
157
60
- use tera :: Context ;
61
- use tera :: Tera ;
158
+ file . sync_all ( ) . expect ( "sync" ) ;
159
+ drop ( file ) ;
62
160
63
- let mut tera = Tera :: default ( ) ;
161
+ debug ! ( "script {}" , render ) ;
64
162
65
- tera. add_raw_template ( "cube.sh" , TEMPLATE )
66
- . expect ( "string compilation" ) ;
163
+ Command :: new ( tmp_file)
164
+ . stdout ( Stdio :: inherit ( ) )
165
+ . stderr ( Stdio :: inherit ( ) )
166
+ . status ( ) ?;
67
167
68
- let mut context = Context :: new ( ) ;
69
- context . insert ( "name" , & option . ctx_name ) ;
70
- context . insert ( "ca" , & load_cert_auth ( ) ) ;
168
+ Ok ( ( ) )
169
+ }
170
+ }
71
171
72
- let render = tera. render ( "cube.sh" , & context) . expect ( "rendering" ) ;
172
+ #[ derive( Debug , Deserialize ) ]
173
+ struct MinikubeNode {
174
+ #[ serde( rename = "IP" ) ]
175
+ ip : IpAddr ,
176
+ #[ serde( rename = "Port" ) ]
177
+ port : u16 ,
178
+ }
73
179
74
- let tmp_file = env:: temp_dir ( ) . join ( "flv_minikube.sh" ) ;
180
+ #[ derive( Debug , Deserialize ) ]
181
+ struct MinikubeConfig {
182
+ #[ serde( rename = "Name" ) ]
183
+ name : String ,
184
+ #[ serde( rename = "Nodes" ) ]
185
+ nodes : Vec < MinikubeNode > ,
186
+ }
75
187
76
- let mut file = OpenOptions :: new ( )
77
- . create ( true )
78
- . write ( true )
79
- . truncate ( true )
80
- . mode ( 0o755 )
81
- . open ( tmp_file. clone ( ) )
82
- . expect ( "temp script can't be created" ) ;
188
+ #[ derive( Debug , Deserialize ) ]
189
+ struct MinikubeProfileWrapper {
190
+ valid : Vec < MinikubeProfileJson > ,
191
+ }
83
192
84
- file. write_all ( render. as_bytes ( ) )
85
- . expect ( "file write failed" ) ;
193
+ #[ derive( Debug , Deserialize ) ]
194
+ struct MinikubeProfileJson {
195
+ #[ serde( rename = "Name" ) ]
196
+ name : String ,
197
+ #[ serde( rename = "Status" ) ]
198
+ status : String ,
199
+ #[ serde( rename = "Config" ) ]
200
+ config : MinikubeConfig ,
201
+ }
86
202
87
- file. sync_all ( ) . expect ( "sync" ) ;
88
- drop ( file) ;
203
+ /// A description of the active Minikube instance, including IP and port
204
+ #[ derive( Debug ) ]
205
+ struct MinikubeProfile {
206
+ /// The name of the minikube profile, usually "minikube"
207
+ name : String ,
208
+ /// The active minikube node, with IP and port
209
+ node : MinikubeNode ,
210
+ }
89
211
90
- debug ! ( "script {}" , render) ;
212
+ impl MinikubeProfile {
213
+ /// Gets minikube's current profile
214
+ fn load ( ) -> Result < MinikubeProfile , ConfigError > {
215
+ let output = Command :: new ( "minikube" )
216
+ . args ( & [ "profile" , "list" , "-o" , "json" ] )
217
+ . output ( ) ?;
218
+ let output_string = String :: from_utf8 ( output. stdout )
219
+ . map_err ( |e| ConfigError :: Other ( format ! ( "`minikube profile list -o json` did not give UTF-8: {}" , e) ) ) ?;
220
+ let profiles: MinikubeProfileWrapper = serde_json:: from_str ( & output_string)
221
+ . map_err ( |e| ConfigError :: Other ( format ! ( "`minikube profile list -o json` did not give valid JSON: {}" , e) ) ) ?;
222
+ let profile_json = profiles. valid . into_iter ( ) . next ( )
223
+ . ok_or ( ConfigError :: Other ( "no valid minikube profiles" . to_string ( ) ) ) ?;
224
+ let node = profile_json. config . nodes . into_iter ( ) . next ( )
225
+ . ok_or ( ConfigError :: Other ( "Minikube has no active nodes" . to_string ( ) ) ) ?;
226
+ let profile = MinikubeProfile {
227
+ name : profile_json. name ,
228
+ node,
229
+ } ;
230
+ Ok ( profile)
231
+ }
232
+
233
+ fn ip ( & self ) -> IpAddr {
234
+ self . node . ip
235
+ }
236
+
237
+ fn port ( & self ) -> u16 {
238
+ self . node . port
239
+ }
240
+
241
+ /// Checks whether the `/etc/hosts` file has an up-to-date entry for minikube
242
+ ///
243
+ /// Returns `Ok(true)` when the hostfile is up-to-date and no action is required.
244
+ ///
245
+ /// Returns `Ok(false)` when the hostfile is out of date or has no `minikubeCA` entry.
246
+ /// In this case, the `/etc/hosts` file needs to be edited.
247
+ ///
248
+ /// Returns `Err(_)` when there is an error detecting the current Minikube ip address
249
+ /// or if there is an error reading the hosts file.
250
+ fn matches_hostfile ( & self ) -> Result < bool , ConfigError > {
251
+ // Check if the /etc/hosts file matches the active node IP
252
+ let matches = get_host_entry ( "minikubeCA" ) ?
253
+ . map ( |ip| ip == self . node . ip )
254
+ . unwrap_or ( false ) ;
255
+ Ok ( matches)
256
+ }
257
+ }
91
258
92
- let output = Command :: new ( tmp_file) . output ( ) . expect ( "cluster command" ) ;
93
- io:: stdout ( ) . write_all ( & output. stdout ) . unwrap ( ) ;
94
- io:: stderr ( ) . write_all ( & output. stderr ) . unwrap ( ) ;
259
+ /// Gets the current entry for a given host in `/etc/hosts` if there is one
260
+ fn get_host_entry ( hostname : & str ) -> Result < Option < IpAddr > , ConfigError > {
261
+ // Get all of the host entries
262
+ let hosts = hostfile:: parse_hostfile ( )
263
+ . map_err ( |e| ConfigError :: Other ( format ! ( "failed to get /etc/hosts entries: {}" , e) ) ) ?;
264
+ // Try to find a host entry with the given hostname
265
+ let minikube_entry = hosts. into_iter ( )
266
+ . find ( |entry| entry. names . iter ( ) . any ( |name| name == hostname) ) ;
267
+ Ok ( minikube_entry. map ( |entry| entry. ip ) )
95
268
}
0 commit comments