diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index bc106263c8..6bed5e0c60 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -1092,3 +1092,11 @@ func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete str } return completeKeyValues(toComplete, kv) } + +// AutocompleteMachineSSH - Autocomplete machine ssh command. +func AutocompleteMachineSSH(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveDefault +} diff --git a/cmd/podman/machine/create.go b/cmd/podman/machine/create.go index 04c5e9e657..1da34327a5 100644 --- a/cmd/podman/machine/create.go +++ b/cmd/podman/machine/create.go @@ -1,3 +1,5 @@ +// +build amd64,linux amd64,darwin arm64,darwin + package machine import ( @@ -6,17 +8,16 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine/qemu" - "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( createCmd = &cobra.Command{ - Use: "create [options] NAME", + Use: "create [options] [NAME]", Short: "Create a vm", - Long: "Create a virtual machine for Podman to run on. Virtual machines are used to run Podman on Macs. ", + Long: "Create a virtual machine for Podman to run on. Virtual machines are used to run Podman.", RunE: create, - Args: cobra.NoArgs, + Args: cobra.MaximumNArgs(1), Example: `podman machine create myvm`, ValidArgsFunction: completion.AutocompleteNone, } @@ -32,7 +33,8 @@ type CreateCLIOptions struct { } var ( - createOpts = CreateCLIOptions{} + createOpts = CreateCLIOptions{} + defaultMachineName string = "podman-machine-default" ) func init() { @@ -59,14 +61,6 @@ func init() { ) _ = createCmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone) - deviceFlagName := "name" - flags.StringVar( - &createOpts.Name, - deviceFlagName, "", - "set vm name", - ) - _ = createCmd.RegisterFlagCompletionFunc(deviceFlagName, completion.AutocompleteDefault) - ImagePathFlagName := "image-path" flags.StringVar(&createOpts.ImagePath, ImagePathFlagName, "", "Path to qcow image") _ = createCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault) @@ -78,9 +72,9 @@ func init() { // TODO should we allow for a users to append to the qemu cmdline? func create(cmd *cobra.Command, args []string) error { - // TODO add ability to create default, not name required - if len(createOpts.Name) < 1 { - return errors.New("required --name not provided") + createOpts.Name = defaultMachineName + if len(args) > 0 { + createOpts.Name = args[0] } vmOpts := machine.CreateOptions{ CPUS: createOpts.CPUS, @@ -95,8 +89,6 @@ func create(cmd *cobra.Command, args []string) error { err error ) switch vmType { - case "foobar": - // do nothing default: // qemu is the default vm, err = qemu.NewMachine(vmOpts) } diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index ce5a3b8897..ed284ee101 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -1,3 +1,5 @@ +// +build amd64,linux amd64,darwin arm64,darwin + package machine import ( @@ -15,7 +17,7 @@ var ( machineCmd = &cobra.Command{ Use: "machine", Short: "Manage a virtual machine", - Long: "Manage a virtual machine. Virtual machines are used to run Podman on Macs.", + Long: "Manage a virtual machine. Virtual machines are used to run Podman.", PersistentPreRunE: noOp, PersistentPostRunE: noOp, RunE: validate.SubCommandExists, diff --git a/cmd/podman/machine/machine_unsupported.go b/cmd/podman/machine/machine_unsupported.go new file mode 100644 index 0000000000..cb16364198 --- /dev/null +++ b/cmd/podman/machine/machine_unsupported.go @@ -0,0 +1,5 @@ +// +build !amd64 arm64,linux amd64,windows + +package machine + +func init() {} diff --git a/cmd/podman/machine/destroy.go b/cmd/podman/machine/remove.go similarity index 63% rename from cmd/podman/machine/destroy.go rename to cmd/podman/machine/remove.go index ab23d607d6..f6ce9e3269 100644 --- a/cmd/podman/machine/destroy.go +++ b/cmd/podman/machine/remove.go @@ -1,3 +1,5 @@ +// +build amd64,linux amd64,darwin arm64,darwin + package machine import ( @@ -7,7 +9,6 @@ import ( "strings" "github.com/containers/common/pkg/completion" - "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/machine" @@ -16,47 +17,43 @@ import ( ) var ( - destroyCmd = &cobra.Command{ - Use: "destroy [options] NAME", - Short: "Destroy an existing machine", - Long: "Destroy an existing machine ", - RunE: destroy, + removeCmd = &cobra.Command{ + Use: "remove [options] NAME", + Short: "Remove an existing machine", + Long: "Remove an existing machine ", + RunE: remove, Args: cobra.ExactArgs(1), - Example: `podman machine destroy myvm`, + Example: `podman machine remove myvm`, ValidArgsFunction: completion.AutocompleteNone, } ) var ( - destoryOptions machine.DestroyOptions + destoryOptions machine.RemoveOptions ) func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: destroyCmd, + Command: removeCmd, Parent: machineCmd, }) - flags := destroyCmd.Flags() + flags := removeCmd.Flags() formatFlagName := "force" - flags.BoolVar(&destoryOptions.Force, formatFlagName, false, "Do not prompt before destroying") - _ = destroyCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat) + flags.BoolVar(&destoryOptions.Force, formatFlagName, false, "Do not prompt before removeing") keysFlagName := "save-keys" flags.BoolVar(&destoryOptions.SaveKeys, keysFlagName, false, "Do not delete SSH keys") - _ = destroyCmd.RegisterFlagCompletionFunc(keysFlagName, common.AutocompleteJSONFormat) ignitionFlagName := "save-ignition" flags.BoolVar(&destoryOptions.SaveIgnition, ignitionFlagName, false, "Do not delete ignition file") - _ = destroyCmd.RegisterFlagCompletionFunc(ignitionFlagName, common.AutocompleteJSONFormat) imageFlagName := "save-image" flags.BoolVar(&destoryOptions.SaveImage, imageFlagName, false, "Do not delete the image file") - _ = destroyCmd.RegisterFlagCompletionFunc(imageFlagName, common.AutocompleteJSONFormat) } -func destroy(cmd *cobra.Command, args []string) error { +func remove(cmd *cobra.Command, args []string) error { var ( err error vm machine.VM @@ -69,7 +66,7 @@ func destroy(cmd *cobra.Command, args []string) error { if err != nil { return err } - confirmationMessage, doIt, err := vm.Destroy(args[0], machine.DestroyOptions{}) + confirmationMessage, doIt, err := vm.Remove(args[0], machine.RemoveOptions{}) if err != nil { return err } diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index 32483f731b..a7111a1958 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -1,7 +1,9 @@ +// +build amd64,linux amd64,darwin arm64,darwin + package machine import ( - "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/machine" @@ -14,13 +16,13 @@ var ( sshCmd = &cobra.Command{ Use: "ssh [options] NAME [COMMAND [ARG ...]]", Short: "SSH into a virtual machine", - Long: "SSH into a podman-managed virtual machine ", + Long: "SSH into a virtual machine ", RunE: ssh, Args: cobra.MinimumNArgs(1), Example: `podman machine ssh myvm podman machine ssh -e myvm echo hello`, - ValidArgsFunction: completion.AutocompleteNone, + ValidArgsFunction: common.AutocompleteMachineSSH, } ) @@ -38,7 +40,6 @@ func init() { flags := sshCmd.Flags() executeFlagName := "execute" flags.BoolVarP(&sshOpts.Execute, executeFlagName, "e", false, "Execute command from args") - _ = sshCmd.RegisterFlagCompletionFunc(executeFlagName, completion.AutocompleteDefault) } func ssh(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index 762639358d..44ade2850f 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -1,3 +1,5 @@ +// +build amd64,linux amd64,darwin arm64,darwin + package machine import ( diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index b6585c296b..35fd4ff95a 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -1,3 +1,5 @@ +// +build amd64,linux amd64,darwin arm64,darwin + package machine import ( diff --git a/docs/source/machine.rst b/docs/source/machine.rst index aefceb3a5d..cf7f72cce7 100644 --- a/docs/source/machine.rst +++ b/docs/source/machine.rst @@ -3,7 +3,7 @@ Machine :doc:`create ` Create a new virtual machine -:doc:`destroy ` Destroy a virtual machine +:doc:`remove ` Remove a virtual machine :doc:`ssh ` SSH into a virtual machine :doc:`start ` Start a virtual machine :doc:`stop ` Stop a virtual machine diff --git a/docs/source/markdown/podman-machine-create.1.md b/docs/source/markdown/podman-machine-create.1.md index 1e199bb772..eb498f2dd9 100644 --- a/docs/source/markdown/podman-machine-create.1.md +++ b/docs/source/markdown/podman-machine-create.1.md @@ -4,7 +4,7 @@ podman\-machine\-create - Create a new virtual machine ## SYNOPSIS -**podman machine create** [*options*] *name* +**podman machine create** [*options*] [*name*] ## DESCRIPTION @@ -34,10 +34,6 @@ Fully qualified path of the uncompressed image file Memory (in MB). -#### **--name** - -Name to assign to the VM - #### **--help** Print usage statement. diff --git a/docs/source/markdown/podman-machine-destroy.1.md b/docs/source/markdown/podman-machine-remove.1.md similarity index 76% rename from docs/source/markdown/podman-machine-destroy.1.md rename to docs/source/markdown/podman-machine-remove.1.md index 7c54214189..07763741d0 100644 --- a/docs/source/markdown/podman-machine-destroy.1.md +++ b/docs/source/markdown/podman-machine-remove.1.md @@ -1,17 +1,17 @@ -% podman-machine-destroy(1) +% podman-machine-remove(1) ## NAME -podman\-machine\-destroy - Destroy a virtual machine +podman\-machine\-remove - Remove a virtual machine ## SYNOPSIS -**podman machine destroy** [*options*] *name* +**podman machine remove** [*options*] *name* ## DESCRIPTION -Destroy a virtual machine and its related files. What is actually deleted +Remove a virtual machine and its related files. What is actually deleted depends on the virtual machine type. For all virtual machines, the generated SSH keys and the podman system connection are deleted. The ignition files -generated for that VM are also destroyed as is its image file on the filesystem. +generated for that VM are also removeed as is its image file on the filesystem. Users get a display of what will be deleted and are required to confirm unless the option `--force` is used. @@ -42,10 +42,10 @@ deleted. ## EXAMPLES -Destroy a VM named "test1" +Remove a VM named "test1" ``` -$ podman machine destroy test1 +$ podman machine remove test1 The following files will be deleted: diff --git a/docs/source/markdown/podman-machine-ssh.1.md b/docs/source/markdown/podman-machine-ssh.1.md index ed35a38e4f..bcecd1010f 100644 --- a/docs/source/markdown/podman-machine-ssh.1.md +++ b/docs/source/markdown/podman-machine-ssh.1.md @@ -16,16 +16,26 @@ tied to the Linux kernel. ## OPTIONS +#### **--execute**, **-e** + +Execute the given command on the VM + #### **--help** Print usage statement. ## EXAMPLES +To get an interactive session with a VM called `myvm`: ``` $ podman machine ssh myvm ``` +To run a command on a VM called `myvm`: +``` +$ podman machine ssh -e myvm -- rpm -q podman +``` + ## SEE ALSO podman-machine (1) diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md index b31d8f7880..52f212cdd9 100644 --- a/docs/source/markdown/podman-machine.1.md +++ b/docs/source/markdown/podman-machine.1.md @@ -14,7 +14,7 @@ podman\-machine - Manage Podman's virtual machine | Command | Man Page | Description | | ------- | ------------------------------------------------------- | ----------------------------- | | create | [podman-machine-create(1)](podman-machine-create.1.md) | Create a new virtual machine | -| destroy | [podman-machine-destroy(1)](podman-machine-destroy.1.md)| Destroy a virtual machine | +| remove | [podman-machine-destroy(1)](podman-machine-remove.1.md)| Remove a virtual machine | | ssh | [podman-machine-ssh.1.md(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | | start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | | stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 148b592e9d..21a8e7a8be 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -22,21 +22,11 @@ import ( "golang.org/x/crypto/ssh/agent" ) -/* - WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING - WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING - - DO NOT MERGE WITHOUT REVERTING THE HACK BELOW - - WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING - WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING -*/ var ( BasePath = &url.URL{ Scheme: "http", Host: "d", - //Path: "/v" + version.APIVersion[version.Libpod][version.CurrentAPI].String() + "/libpod", - Path: "/v3.0.0/libpod", + Path: "/v" + version.APIVersion[version.Libpod][version.CurrentAPI].String() + "/libpod", } ) diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 2a70b8ff7e..242401ab40 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -50,7 +50,7 @@ type StartOptions struct{} type StopOptions struct{} -type DestroyOptions struct { +type RemoveOptions struct { Force bool SaveKeys bool SaveImage bool @@ -59,7 +59,7 @@ type DestroyOptions struct { type VM interface { Create(opts CreateOptions) error - Destroy(name string, opts DestroyOptions) (string, func() error, error) + Remove(name string, opts RemoveOptions) (string, func() error, error) SSH(name string, opts SSHOptions) error Start(name string, opts StartOptions) error Stop(name string, opts StopOptions) error @@ -70,33 +70,18 @@ type DistributionDownload interface { Get() *Download } -// TODO is this even needed? -type TestVM struct{} - -func (vm *TestVM) Create(opts CreateOptions) error { - return nil -} - -func (vm *TestVM) Start(name string, opts StartOptions) error { - return nil -} -func (vm *TestVM) Stop(name string, opts StopOptions) error { - return nil -} - func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { userInfo := url.User(userName) uri := url.URL{ - Scheme: "ssh", - Opaque: "", - User: userInfo, - Host: host, - Path: path, - RawPath: "", - ForceQuery: false, - RawQuery: "", - Fragment: "", - RawFragment: "", + Scheme: "ssh", + Opaque: "", + User: userInfo, + Host: host, + Path: path, + RawPath: "", + ForceQuery: false, + RawQuery: "", + Fragment: "", } if len(port) > 0 { uri.Host = net.JoinHostPort(uri.Hostname(), port) diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 8bbad458f7..0c6a2485ea 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -11,10 +11,8 @@ import ( "strings" "github.com/containers/storage/pkg/archive" - - "github.com/sirupsen/logrus" - digest "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" ) // These should eventually be moved into machine/qemu as diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 30d96ce08b..504b64bd51 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -10,8 +10,9 @@ import ( "strconv" "time" + "github.com/containers/podman/v3/utils" + "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/storage/pkg/homedir" "github.com/digitalocean/go-qemu/qmp" "github.com/pkg/errors" @@ -56,7 +57,7 @@ func NewMachine(opts machine.CreateOptions) (machine.VM, error) { } // Add a random port for ssh - port, err := specgen.GetRandomPort() + port, err := utils.GetRandomPort() if err != nil { return nil, err } @@ -170,7 +171,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { attr := new(os.ProcAttr) files := []*os.File{os.Stdin, os.Stdout, os.Stderr} attr.Files = files - fmt.Print(v.CmdLine) + logrus.Debug(v.CmdLine) _, err = os.StartProcess(v.CmdLine[0], v.CmdLine, attr) return err } @@ -211,22 +212,29 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { // NewQMPMonitor creates the monitor subsection of our vm func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) { - rtDir, err := getDataDir() + rtDir, err := getSocketDir() if err != nil { return Monitor{}, err } + rtDir = filepath.Join(rtDir, "podman") + if _, err := os.Stat(filepath.Join(rtDir)); os.IsNotExist(err) { + // TODO 0644 is fine on linux but macos is weird + if err := os.MkdirAll(rtDir, 0755); err != nil { + return Monitor{}, err + } + } if timeout == 0 { timeout = defaultQMPTimeout } monitor := Monitor{ Network: network, - Address: filepath.Join(rtDir, "podman", "qmp_"+name+".sock"), + Address: filepath.Join(rtDir, "qmp_"+name+".sock"), Timeout: timeout, } return monitor, nil } -func (v *MachineVM) Destroy(name string, opts machine.DestroyOptions) (string, func() error, error) { +func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) { var ( files []string ) diff --git a/pkg/machine/qemu/options_darwin.go b/pkg/machine/qemu/options_darwin.go new file mode 100644 index 0000000000..46ccf24cbc --- /dev/null +++ b/pkg/machine/qemu/options_darwin.go @@ -0,0 +1,15 @@ +package qemu + +import ( + "os" + + "github.com/pkg/errors" +) + +func getSocketDir() (string, error) { + tmpDir, ok := os.LookupEnv("TMPDIR") + if !ok { + return "", errors.New("unable to resolve TMPDIR") + } + return tmpDir, nil +} diff --git a/pkg/machine/qemu/options_darwin_amd64.go b/pkg/machine/qemu/options_darwin_amd64.go new file mode 100644 index 0000000000..69f7982b25 --- /dev/null +++ b/pkg/machine/qemu/options_darwin_amd64.go @@ -0,0 +1,18 @@ +package qemu + +var ( + QemuCommand = "qemu-system-x86_64" +) + +func (v *MachineVM) addArchOptions() []string { + opts := []string{"-cpu", "host"} + return opts +} + +func (v *MachineVM) prepare() error { + return nil +} + +func (v *MachineVM) archRemovalFiles() []string { + return []string{} +} diff --git a/pkg/machine/qemu/options_arm64.go b/pkg/machine/qemu/options_darwin_arm64.go similarity index 93% rename from pkg/machine/qemu/options_arm64.go rename to pkg/machine/qemu/options_darwin_arm64.go index c5b0ea16b0..7513b30487 100644 --- a/pkg/machine/qemu/options_arm64.go +++ b/pkg/machine/qemu/options_darwin_arm64.go @@ -31,10 +31,6 @@ func (v *MachineVM) archRemovalFiles() []string { return []string{ovmDir} } -func getDataDir() (string, error) { - return "/tmp", nil -} - func getOvmfDir(imagePath, vmName string) string { return filepath.Join(filepath.Dir(imagePath), vmName+"_ovmf_vars.fd") } diff --git a/pkg/machine/qemu/options_linux.go b/pkg/machine/qemu/options_linux.go new file mode 100644 index 0000000000..0a2e40d8fd --- /dev/null +++ b/pkg/machine/qemu/options_linux.go @@ -0,0 +1,7 @@ +package qemu + +import "github.com/containers/podman/v3/pkg/util" + +func getSocketDir() (string, error) { + return util.GetRuntimeDir() +} diff --git a/pkg/machine/qemu/options_amd64.go b/pkg/machine/qemu/options_linux_amd64.go similarity index 68% rename from pkg/machine/qemu/options_amd64.go rename to pkg/machine/qemu/options_linux_amd64.go index 85a2c4b3ed..cc0a4bab2a 100644 --- a/pkg/machine/qemu/options_amd64.go +++ b/pkg/machine/qemu/options_linux_amd64.go @@ -1,9 +1,5 @@ package qemu -import ( - "github.com/containers/podman/v3/pkg/util" -) - var ( QemuCommand = "qemu-kvm" ) @@ -20,7 +16,3 @@ func (v *MachineVM) prepare() error { func (v *MachineVM) archRemovalFiles() []string { return []string{} } - -func getDataDir() (string, error) { - return util.GetRuntimeDir() -} diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index d5d779c8ff..678e36a708 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -6,6 +6,8 @@ import ( "strconv" "strings" + "github.com/containers/podman/v3/utils" + "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/cri-o/ocicni/pkg/ocicni" @@ -218,7 +220,7 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, // Only get a random candidate for single entries or the start // of a range. Otherwise we just increment the candidate. if !tmp.isInRange || tmp.startOfRange { - candidate, err = specgen.GetRandomPort() + candidate, err = utils.GetRandomPort() if err != nil { return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort) } @@ -344,7 +346,7 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *imag for hostPort == 0 && tries > 0 { // We can't select a specific protocol, which is // unfortunate for the UDP case. - candidate, err := specgen.GetRandomPort() + candidate, err := utils.GetRandomPort() if err != nil { return nil, err } diff --git a/pkg/specgen/ports.go b/utils/ports.go similarity index 97% rename from pkg/specgen/ports.go rename to utils/ports.go index 940b2a5646..0a4f67dcc1 100644 --- a/pkg/specgen/ports.go +++ b/utils/ports.go @@ -1,4 +1,4 @@ -package specgen +package utils import ( "net"