Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: Add kraft setup command #257

Open
wants to merge 1 commit into
base: staging
Choose a base branch
from

Conversation

StefanJum
Copy link
Member

The kraft setup command will install the required dependencies for building and running unikernels. It will also check the virtualization support of the environment, check if we are running kraft from inside a conainter or a virtual machine and give a warning about enabling nested virtualization

Prerequisite checklist

  • Read the contribution guidelines regarding submitting new changes to the project;
  • Tested your changes against relevant architectures and platforms;
  • Ran make fmt on your commit series before opening this PR;
  • Updated relevant documentation.

Description of changes

@craciunoiuc
Copy link
Member

conainter -> container in the commit message 😄

@StefanJum
Copy link
Member Author

conainter -> container in the commit message smile

Done, sorry 😆

Copy link
Member

@craciunoiuc craciunoiuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @StefanJum

Added some quick notes from my side.

One thing I don't get here is what setup should be doing.

I was under the impression that it should set up the environment, like the config options and other os-specific options.

The installation I thought was handled by the install script:
https://get.kraftkit.sh

Also tagging @nderjung

cmd/kraft/kraft.go Outdated Show resolved Hide resolved
cmd/kraft/setup/setup.go Outdated Show resolved Hide resolved
Comment on lines 86 to 56
req := []string{
"build-essential",
"libncurses-dev",
"libyaml-dev",
"flex",
"git",
"wget",
"socat",
"bison",
"unzip",
"uuid-runtime",
"python3",
"python3-setuptools",
"python3-pip",
"qemu-kvm",
"qemu-system-x86",
"sgabios",
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe we should restrict those by versions? Not sure.

At least a lower-bound we should set

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these requirements actually necessary? I believe the list is now much smaller now. Plus this should be "switchable" the user may not necessarily want qemu :) so a VMM option, determination, should append this or prompt for it

cmd/kraft/setup/setup.go Outdated Show resolved Hide resolved
cmd/kraft/setup/setup.go Outdated Show resolved Hide resolved
@StefanJum
Copy link
Member Author

Hey @StefanJum

Added some quick notes from my side.

One thing I don't get here is what setup should be doing.

I was under the impression that it should set up the environment, like the config options and other os-specific options.

The installation I thought was handled by the install script: https://get.kraftkit.sh

Also tagging @nderjung

Thanks, I'll check the comments.

From what I understood kraft setup should prepare the environment for building and running unikernels.
That would mean installing the dependencies and detecting the virtualization support (xen, kvm etc.) in order to use that when configuring/running the applications.
I think the best way to do that is by saving the found virtualization solution in the kraftkit config file, but the installation of all requirements would still be necessary as a first step.

Does the install script add all the requirements for building and running unikernels or just for building/running kraft?

@nderjung please correct me if I'm wrong.

@StefanJum StefanJum force-pushed the StefanJum/add-setup-cmd branch 3 times, most recently from ee6a206 to 2c3d9f3 Compare March 4, 2023 09:57
@craciunoiuc
Copy link
Member

Actually you are right.

What I wanted to say is that maybe we should be kind-of merging these two together somehow.

From my perspective when you first run a tool you should have all dependencies already solved. I would just add an extra check in the install script to ask "Do you want to install building and running dependencies"

I'm fine either way. Let's see what Alex has to say

Copy link
Member

@nderjung nderjung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @StefanJum. Thanks for putting this together.

To be inline with how logic is approached in the cmd/ directory, each subcommand program essentially calls functionality exposed by the "KraftKit framework". I think the setup cmd should be similar and therefore the logic for actually performing the checks, running any installation, etc. should occur as an exposed API from a different package. The goal here is to keep the file cmd/setup/setup.go as simple as possible.

I suggest re-working the logic into a new package, kraftkit.sh/internal/setup. as is suggested by the corresponding issue for this feature.

Towards a re-usable API, i'd recommend starting simply and with something like the following:

cmd/setup/setup.go:

import "kraftkit.sh/internal/setup"

type Setup struct {
  OS   string `long:"os" usage:"Force the host OS"`
  Arch string `long:"arch" usage:"Force the architecture of the host"`
  VMM  string `long:"vmm" usage:"Force the use of a specific VMM on the host"`
  PM   string `long:"pm" usage:"Force the use of a specific package manager on the host"`
}

// [...]

var sopts []setup.SetupOption

if opts.OS != "" {
  sopts = append(sopts, setup.WithOS(opts.WithOS))
}

// [...etc...]

if err := setup.Setup(ctx, ...sopts); err != nil {
  return err
}

internal/setup/setup.go:

type Setup struct {
  arch *HostArch
  os   *HostOS
  pm   *HostPackageManager
  vmm  *HostVMM
}

func Setup(ctx context.Context, sopts ...SetupOption) error {
  s := Setup{}
  for _, opts := range sopts {
    if err := opt(&s); err != nil {
      return err
    }
  }

  var err error

  if s.arch == nil {
    s.arch, err = DetectHostArch()
    if err != nil {
      return err
    }
  }
    
  // [...]
}

internal/setup/options.go:

// [...]

type SetupOption func(*Setup) error

func WithOS(os string) SetupOption {
 // This should check if the provided `os` variable is valid in format and
 // in support. E.g. providing `ubuntu` and `linux/ubuntu` should work,
 // but perhaps providing `windows`, `windows/choco` would not.
}

Additionally, each os/distribution should be a "provider" following an implementation to the following abstract interface:

type HostPackageManager interface {
  Install(context.Context, ...string) error
}

The above interface needs a bit more thought, as @craciunoiuc has mentioned, because versions need to be listed. I can imagine the following could be a way to provide the listing:

internal/setup/setup_ubuntu.go:

var DistUbuntu = [...]HostPackage{
  HostPackage{"package",  GT, "1.0.0"},
};

That said, I would be surprised if there were no existing Go packages that do not do something similar to what we're trying to achieve.

Additional comments:

  • Please can you use the updated copyright preamble, here is an example of what it looks like;
  • Use iostreams.G(ctx).Out when redirecting information to the user via fmt.Fprintf, this is the global access point to os.Stdout, which keeps its configuration and possible redirection globally configurable;
  • Check config.G(ctx).NoPrompt to determine whether to prompt the user for questions, e.g. Would you like to continue? [Yn];
  • Use kraftkit.sh/exec for executing commands and not the built-in os/exec package.

@nderjung
Copy link
Member

nderjung commented Mar 5, 2023

To address the comments by @craciunoiuc, the get.kraftkit.sh program can and should recommend additional programs through the relevant package manager, e.g. "APT recommends". Whilst this is the recommended method for installation and any dependencies, a built-in command, kraft setup, can not only determine if and which binaries are necessary for a unikraft build, e.g. we can essentially run "which" internally, but should also help and aid in "setting up" the host environment. Along these lines, we can additionally check the host for virtualization features.

In my mind, following an installation from get.kraftkit.sh, would be an immediate execution of kraft setup, which guides the user through host-setup. In a non-standard install, e.g. by pulling the binary directly from the releases page, the kraft setup command enables access to such installation.

I also imagine an extension to kraft setup in the future which would guide the user through setting up the global configuration with potential values. We can detect a "first installation" by whether the configuration file has been written. We could inject inside of cmd/kraft/kraft.go a check, for example:

import "kraftkit.sh/internal/setup"

// [...]

func main() {
  // [...]
  
  if isFirstSetup, _ := setup.IsFirstUse(ctx); isFirstSetup {
    if err := setup.Setup(ctx); err != nil {
      fmt.Printf("could not perform first-use setup: %v", err)
      os.Exit(1)
    }
  }

  // [...]

  cmdfactory.Main(ctx)
}

@craciunoiuc
Copy link
Member

Yes, you are right. I forgot that many people actually pull the binary from the releases page.

The `kraft setup` command will install the required dependencies for building and
running unikernels. It will also check the virtualization support of the
environment, check if we are running kraft from inside a container or a virtual
machine and give a warning about enabling nested virtualization

Signed-off-by: Stefan Jumarea <stefanjumarea02@gmail.com>
@StefanJum
Copy link
Member Author

@nderjung I did a quick refactoring based on your comments, let me know how it looks.

@StefanJum StefanJum requested a review from nderjung March 15, 2023 11:48
Copy link
Member

@nderjung nderjung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @StefanJum, thanks for the update, I can see this is still WIP, however, see comments inline.

Comment on lines +17 to +20
WithOS string `long:"with-os" usage:"Force the host OS"`
WithArch string `long:"with-arch" usage:"Force the architecture of the host"`
WithVMM string `long:"with-vmm" usage:"Force the use of a specific VMM on the host"`
WithPM string `long:"with-pm" usage:"Force the use of a specific package manager on the host"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-	WithOS   string `long:"with-os" usage:"Force the host OS"`
-	WithArch string `long:"with-arch" usage:"Force the architecture of the host"`
-	WithVMM  string `long:"with-vmm" usage:"Force the use of a specific VMM on the host"`
-	WithPM   string `long:"with-pm" usage:"Force the use of a specific package manager on the host"`
+	OS   string `long:"os" usage:"Force the host OS"`
+	Arch string `long:"arch" usage:"Force the architecture of the host"`
+	VMM  string `long:"vmm" usage:"Force the use of a specific VMM on the host"`
+	PM   string `long:"pm" usage:"Force the use of a specific package manager on the host"`

return cmdfactory.New(&Setup{}, cobra.Command{
Short: "Setup the working environment for building and running unikernels",
Use: "setup",
Aliases: []string{"sup"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-		Aliases: []string{"sup"},

Short: "Setup the working environment for building and running unikernels",
Use: "setup",
Aliases: []string{"sup"},
Args: cmdfactory.MaxDirArgs(0),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-		Args:    cmdfactory.MaxDirArgs(0),
+		Args:    cobra. NoArgs(),

# Setup the environment
$ kraft setup`),
Annotations: map[string]string{
"help:group": "build",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-			"help:group": "build",
+			cmdfactory.AnnotationHelpGroup: "build",

Comment on lines +47 to +50
sopts = append(sopts, setup.WithOS(opts.WithOS))
} else {
sopts = append(sopts, setup.WithDetectHostOS())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-		sopts = append(sopts, setup.WithOS(opts.WithOS))
-	} else {
-		sopts = append(sopts, setup.WithDetectHostOS())
-	}
+		sopts = append(sopts, setup.WithOS(opts.OS))
+	}

Comment on lines +13 to +16
WithOS string `long:"with-os" usage:"Force the host OS"`
WithArch string `long:"with-arch" usage:"Force the architecture of the host"`
WithVMM string `long:"with-vmm" usage:"Force the use of a specific VMM on the host"`
WithPM string `long:"with-pm" usage:"Force the use of a specific package manager on the host"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-	WithOS   string `long:"with-os" usage:"Force the host OS"`
-	WithArch string `long:"with-arch" usage:"Force the architecture of the host"`
-	WithVMM  string `long:"with-vmm" usage:"Force the use of a specific VMM on the host"`
-	WithPM   string `long:"with-pm" usage:"Force the use of a specific package manager on the host"`
+ os   string
+ arch string
+ vmm  string
+ pm   string

Comment on lines +19 to +21
func DoSetup(ctx context.Context, sopts ...SetupOption) error {
s := Setup{}
for _, opts := range sopts {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- func DoSetup(ctx context.Context, sopts ...SetupOption) error {
+ func Setup(ctx context.Context, sopts ...SetupOption) error {
    s := Setup{}
+
    for _, opts := range sopts {

Comment on lines +87 to +99
func detect_arch() string {
os_info, _ := host.Info()
arch := os_info.KernelArch

return arch
}

func detect_vmm() string {
os_virt, role, _ := host.Virtualization()
vmm := os_virt + "/" + role

return vmm
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functions in Go are never snake_case and are instead camelBack.

Additionally, the host.Virtualization() method is a nice find. Perhaps it's best to serialize all possible outputs into a global list of supported values.

Comment on lines +101 to +103
func NotSupported(*Setup) error {
return nil
}
Copy link
Member

@nderjung nderjung Mar 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- func NotSupported(*Setup) error {
- 	return nil
- }
+ const (
+    ErrNotSupportedVMM = errors.New("virtualization system not supported")
+    ErrNotSupportedPM  = errors.New("package manager not supported")
+    ErrNotSupportedOS  = errors.New("OS not supported")
+ )

(and place at the top of the file)

@@ -77,6 +78,7 @@ func New() *cobra.Command {
cmd.AddCommand(ps.New())
cmd.AddCommand(rm.New())
cmd.AddCommand(run.New())
cmd.AddCommand(setup.New())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the misc section

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants