@@ -88,6 +88,17 @@ func RunHostPreflights(c *cli.Context) error {
8888 if err != nil {
8989 return fmt .Errorf ("unable to read host preflights: %w" , err )
9090 }
91+
92+ chpfs , err := preflights .GetClusterHostPreflights (c .Context )
93+ if err != nil {
94+ return fmt .Errorf ("unable to get cluster host preflights: %w" , err )
95+ }
96+
97+ for _ , h := range chpfs {
98+ hpf .Collectors = append (hpf .Collectors , h .Spec .Collectors ... )
99+ hpf .Analyzers = append (hpf .Analyzers , h .Spec .Analyzers ... )
100+ }
101+
91102 return runHostPreflights (c , hpf )
92103}
93104
@@ -96,25 +107,61 @@ func runHostPreflights(c *cli.Context, hpf *v1beta2.HostPreflightSpec) error {
96107 return nil
97108 }
98109 pb := spinner .Start ()
99- pb .Infof ("Running host preflights on node" )
110+ if c .Bool ("skip-host-preflights" ) {
111+ pb .Infof ("Skipping host preflights" )
112+ pb .Close ()
113+ return nil
114+ }
115+ pb .Infof ("Running host preflights" )
100116 output , err := preflights .Run (c .Context , hpf )
101117 if err != nil {
102118 pb .CloseWithError ()
103119 return fmt .Errorf ("host preflights failed: %w" , err )
104120 }
121+
122+ err = output .SaveToDisk ()
123+ if err != nil {
124+ pb .CloseWithError ()
125+ return fmt .Errorf ("failed to save preflights output: %w" , err )
126+ }
127+
105128 if output .HasFail () {
129+ s := "failures"
130+ if len (output .Fail ) == 1 {
131+ s = "failure"
132+ }
133+ msg := fmt .Sprintf ("Host preflights have %d %s" , len (output .Fail ), s )
134+ if output .HasWarn () {
135+ s = "warnings"
136+ if len (output .Warn ) == 1 {
137+ s = "warning"
138+ }
139+ msg += fmt .Sprintf (" and %d %s" , len (output .Warn ), s )
140+ }
141+
142+ pb .Errorf (msg )
106143 pb .CloseWithError ()
107- output .PrintTable ()
144+ output .PrintTableWithoutInfo ()
108145 return fmt .Errorf ("preflights haven't passed on the host" )
109146 }
110- if ! output .HasWarn () || c .Bool ("no-prompt" ) {
147+ if ! output .HasWarn () {
148+ pb .Close ()
149+ return nil
150+ }
151+ if c .Bool ("no-prompt" ) {
152+ // We have warnings but we are not in interactive mode
153+ // so we just print the warnings and continue
111154 pb .Close ()
112- output .PrintTable ()
155+ output .PrintTableWithoutInfo ()
113156 return nil
114157 }
158+ s := "warnings"
159+ if len (output .Warn ) == 1 {
160+ s = "warning"
161+ }
162+ pb .Warnf ("Host preflights have %d %s" , len (output .Warn ), s )
115163 pb .CloseWithError ()
116- output .PrintTable ()
117- logrus .Infof ("Host preflights have warnings" )
164+ output .PrintTableWithoutInfo ()
118165 if ! prompts .New ().Confirm ("Do you want to continue ?" , false ) {
119166 return fmt .Errorf ("user aborted" )
120167 }
@@ -136,7 +183,7 @@ func isAlreadyInstalled() (bool, error) {
136183 }
137184}
138185
139- func checkLicenseMatches (c * cli. Context ) error {
186+ func checkLicenseMatches (licenseFile string ) error {
140187 rel , err := release .GetChannelRelease ()
141188 if err != nil {
142189 return fmt .Errorf ("failed to get release from binary: %w" , err ) // this should only be if the release is malformed
@@ -146,20 +193,20 @@ func checkLicenseMatches(c *cli.Context) error {
146193 // 1. no release and no license, which is OK
147194 // 2. no license and a release, which is not OK
148195 // 3. a license and no release, which is not OK
149- if rel == nil && c . String ( "license" ) == "" {
196+ if rel == nil && licenseFile == "" {
150197 // no license and no release, this is OK
151198 return nil
152- } else if rel == nil && c . String ( "license" ) != "" {
199+ } else if rel == nil && licenseFile != "" {
153200 // license is present but no release, this means we would install without vendor charts and k0s overrides
154201 return fmt .Errorf ("a license was provided but no release was found in binary, please rerun without the license flag" )
155- } else if rel != nil && c . String ( "license" ) == "" {
202+ } else if rel != nil && licenseFile == "" {
156203 // release is present but no license, this is not OK
157204 return fmt .Errorf ("no license was provided for %s and one is required, please rerun with '--license <path to license file>'" , rel .AppSlug )
158205 }
159206
160- license , err := helpers .ParseLicense (c . String ( "license" ) )
207+ license , err := helpers .ParseLicense (licenseFile )
161208 if err != nil {
162- return fmt .Errorf ("unable to parse the license file at %q, please ensure it is not corrupt: %w" , c . String ( "license" ) , err )
209+ return fmt .Errorf ("unable to parse the license file at %q, please ensure it is not corrupt: %w" , licenseFile , err )
163210 }
164211
165212 // Check if the license matches the application version data
@@ -173,6 +220,21 @@ func checkLicenseMatches(c *cli.Context) error {
173220 return fmt .Errorf ("license channel %s (%s) does not match binary channel %s, please provide the correct license" , license .Spec .ChannelID , license .Spec .ChannelName , rel .ChannelID )
174221 }
175222
223+ if license .Spec .Entitlements ["expires_at" ].Value .StrVal != "" {
224+ // read the expiration date, and check it against the current date
225+ expiration , err := time .Parse (time .RFC3339 , license .Spec .Entitlements ["expires_at" ].Value .StrVal )
226+ if err != nil {
227+ return fmt .Errorf ("unable to parse expiration date: %w" , err )
228+ }
229+ if time .Now ().After (expiration ) {
230+ return fmt .Errorf ("license expired on %s, please provide a valid license" , expiration )
231+ }
232+ }
233+
234+ if ! license .Spec .IsEmbeddedClusterDownloadEnabled {
235+ return fmt .Errorf ("license does not have embedded cluster enabled, please provide a valid license" )
236+ }
237+
176238 return nil
177239}
178240
@@ -375,7 +437,7 @@ func waitForK0s() error {
375437}
376438
377439// runOutro calls Outro() in all enabled addons by means of Applier.
378- func runOutro (c * cli.Context ) error {
440+ func runOutro (c * cli.Context , adminConsolePwd string ) error {
379441 os .Setenv ("KUBECONFIG" , defaults .PathToKubeConfig ())
380442 opts := []addons.Option {}
381443
@@ -398,12 +460,33 @@ func runOutro(c *cli.Context) error {
398460 if ab := c .String ("airgap-bundle" ); ab != "" {
399461 opts = append (opts , addons .WithAirgapBundle (ab ))
400462 }
463+ opts = append (opts , addons .WithAdminConsolePassword (adminConsolePwd ))
401464 if c .String ("http-proxy" ) != "" || c .String ("https-proxy" ) != "" || c .String ("no-proxy" ) != "" {
402465 opts = append (opts , addons .WithProxyFromArgs (c .String ("http-proxy" ), c .String ("https-proxy" ), c .String ("no-proxy" )))
403466 }
404467 return addons .NewApplier (opts ... ).Outro (c .Context )
405468}
406469
470+ func askAdminConsolePassword (c * cli.Context ) (string , error ) {
471+ defaultPass := "password"
472+ if c .Bool ("no-prompt" ) {
473+ logrus .Infof ("Admin Console password set to: %s" , defaultPass )
474+ return defaultPass , nil
475+ }
476+ maxTries := 3
477+ for i := 0 ; i < maxTries ; i ++ {
478+ promptA := prompts .New ().Password ("Enter an Admin Console password:" )
479+ promptB := prompts .New ().Password ("Confirm password:" )
480+
481+ if promptA == promptB {
482+ // TODO: Should we add extra password validation here? e.g length, complexity etc
483+ return promptA , nil
484+ }
485+ logrus .Info ("Passwords don't match, please try again." )
486+ }
487+ return "" , fmt .Errorf ("unable to set Admin Console password after %d tries" , maxTries )
488+ }
489+
407490// installCommands executes the "install" command. This will ensure that a k0s.yaml file exists
408491// and then run `k0s install` to apply the cluster. Once this is finished then a "kubeconfig"
409492// file is created. Resulting kubeconfig is stored in the configuration dir.
@@ -461,6 +544,11 @@ var installCommand = &cli.Command{
461544 Usage : "Use the system proxy settings for the install operation. These variables are currently only passed through to Velero and the Admin Console." ,
462545 Hidden : true ,
463546 },
547+ & cli.BoolFlag {
548+ Name : "skip-host-preflights" ,
549+ Usage : "Skip host preflight checks. This is not recommended unless you are sure your system is compatible." ,
550+ Value : false ,
551+ },
464552 },
465553 Action : func (c * cli.Context ) error {
466554 logrus .Debugf ("checking if %s is already installed" , binName )
@@ -479,7 +567,7 @@ var installCommand = &cli.Command{
479567 return fmt .Errorf ("unable to configure network manager: %w" , err )
480568 }
481569 logrus .Debugf ("checking license matches" )
482- if err := checkLicenseMatches (c ); err != nil {
570+ if err := checkLicenseMatches (c . String ( "license" ) ); err != nil {
483571 metricErr := fmt .Errorf ("unable to check license: %w" , err )
484572 metrics .ReportApplyFinished (c , metricErr )
485573 return err // do not return the metricErr, as we want the user to see the error message without a prefix
@@ -495,6 +583,11 @@ var installCommand = &cli.Command{
495583 metrics .ReportApplyFinished (c , err )
496584 return err
497585 }
586+ adminConsolePwd , err := askAdminConsolePassword (c )
587+ if err != nil {
588+ metrics .ReportApplyFinished (c , err )
589+ return err
590+ }
498591 logrus .Debugf ("running host preflights" )
499592 if err := RunHostPreflights (c ); err != nil {
500593 err := fmt .Errorf ("unable to finish preflight checks: %w" , err )
@@ -534,7 +627,7 @@ var installCommand = &cli.Command{
534627 return err
535628 }
536629 logrus .Debugf ("running outro" )
537- if err := runOutro (c ); err != nil {
630+ if err := runOutro (c , adminConsolePwd ); err != nil {
538631 metrics .ReportApplyFinished (c , err )
539632 return err
540633 }
0 commit comments