forked from cnrancher/autok3s
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(airgap): Basic airgap cli function
As design, following actions are supported: - create - update - remove - ls - import - export - update-install-script The downloaded resource will be stored in package dir under CfgPath. The design can be found in cnrancher#480.
- Loading branch information
1 parent
e6ca11a
commit b921cce
Showing
27 changed files
with
1,541 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,4 +34,7 @@ static/ | |
upx*.tar.xz | ||
upx*/ | ||
.vscode/ | ||
.dapper | ||
.dapper | ||
|
||
# Others | ||
pkg/settings/install.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package airgap | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
airgap = &cobra.Command{ | ||
Use: "airgap", | ||
Short: "The airgap packages management.", | ||
Long: "The airgap command manages the airgap package for k3s.", | ||
} | ||
) | ||
|
||
func Command() *cobra.Command { | ||
airgap.AddCommand( | ||
listCmd, | ||
createCmd, | ||
removeCmd, | ||
updateCmd, | ||
importCmd, | ||
exportCmd, | ||
updateScriptCmd, | ||
) | ||
return airgap | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package airgap | ||
|
||
import ( | ||
"fmt" | ||
|
||
pkgairgap "github.com/cnrancher/autok3s/pkg/airgap" | ||
"github.com/cnrancher/autok3s/pkg/common" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
"k8s.io/apimachinery/pkg/util/validation" | ||
) | ||
|
||
var ( | ||
airgapFlags = flags{} | ||
) | ||
|
||
type flags struct { | ||
isForce bool | ||
isJSON bool | ||
K3sVersion string | ||
Archs []string | ||
} | ||
|
||
func getArchSelect(def []string) *survey.MultiSelect { | ||
return &survey.MultiSelect{ | ||
Default: def, | ||
Message: "What arch do you prefer?", | ||
Options: pkgairgap.GetValidatedArchs(), | ||
} | ||
} | ||
|
||
func validateName(name string) error { | ||
if name == "" { | ||
return errNameRequire | ||
} | ||
pkgs, _ := common.DefaultDB.ListPackages(&name) | ||
if len(pkgs) > 0 { | ||
return fmt.Errorf("package name %s already exists", name) | ||
} | ||
if errs := validation.IsDNS1123Subdomain(name); len(errs) > 0 { | ||
return fmt.Errorf("name is not validated %s, %v", name, errs) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package airgap | ||
|
||
import ( | ||
"sort" | ||
"strings" | ||
|
||
pkgairgap "github.com/cnrancher/autok3s/pkg/airgap" | ||
"github.com/cnrancher/autok3s/pkg/common" | ||
"github.com/cnrancher/autok3s/pkg/utils" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var createCmd = &cobra.Command{ | ||
Use: "create <name>", | ||
Short: "Create a new airgap package and will download related resources from internet.", | ||
Args: cobra.ExactArgs(1), | ||
PreRun: func(cmd *cobra.Command, args []string) { | ||
sort.Strings(airgapFlags.Archs) | ||
}, | ||
RunE: create, | ||
} | ||
|
||
func init() { | ||
createCmd.Flags().StringVarP(&airgapFlags.K3sVersion, "k3s-version", "v", airgapFlags.K3sVersion, "The version of k3s to store airgap resources.") | ||
createCmd.Flags().StringArrayVar(&airgapFlags.Archs, "arch", airgapFlags.Archs, "The archs of the k3s version. Following archs are support: "+strings.Join(pkgairgap.GetValidatedArchs(), ",")+".") | ||
} | ||
|
||
func create(cmd *cobra.Command, args []string) error { | ||
name := args[0] | ||
if err := validateName(name); err != nil { | ||
return err | ||
} | ||
|
||
var qs []*survey.Question | ||
|
||
if airgapFlags.K3sVersion == "" { | ||
if !utils.IsTerm() { | ||
return errors.New("k3s-version flags is required") | ||
} | ||
qs = append(qs, &survey.Question{ | ||
Name: "k3sVersion", | ||
Prompt: &survey.Input{Message: "K3s Version?"}, | ||
Validate: survey.Required, | ||
}) | ||
} | ||
|
||
if len(airgapFlags.Archs) == 0 { | ||
if !utils.IsTerm() { | ||
return errors.New("at least one arch should be specified") | ||
} | ||
qs = append(qs, &survey.Question{ | ||
Name: "archs", | ||
Prompt: getArchSelect([]string{}), | ||
Validate: survey.Required, | ||
}) | ||
} | ||
|
||
if err := survey.Ask(qs, &airgapFlags); err != nil { | ||
return err | ||
} | ||
|
||
if err := pkgairgap.ValidateArchs(airgapFlags.Archs); err != nil { | ||
return err | ||
} | ||
|
||
pkg := common.Package{ | ||
Name: name, | ||
K3sVersion: airgapFlags.K3sVersion, | ||
Archs: airgapFlags.Archs, | ||
State: common.PackageOutOfSync, | ||
} | ||
|
||
if err := common.DefaultDB.SavePackage(pkg); err != nil { | ||
return err | ||
} | ||
cmd.Printf("airgap package %s record created, prepare to download\n", pkg.Name) | ||
downloader, err := pkgairgap.NewDownloader(pkg) | ||
if err != nil { | ||
return errors.Wrapf(err, "failed to start download process for package %s", pkg.Name) | ||
} | ||
path, err := downloader.DownloadPackage() | ||
if err != nil { | ||
return errors.Wrapf(err, "failed to download package %s", pkg.Name) | ||
} | ||
pkg.FilePath = path | ||
pkg.State = common.PackageActive | ||
if err := common.DefaultDB.SavePackage(pkg); err != nil { | ||
return err | ||
} | ||
cmd.Printf("airgap package %s created and stored in path %s\n", name, path) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package airgap | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
pkgairgap "github.com/cnrancher/autok3s/pkg/airgap" | ||
"github.com/cnrancher/autok3s/pkg/common" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
"gorm.io/gorm" | ||
) | ||
|
||
var ( | ||
exportCmd = &cobra.Command{ | ||
Use: "export <name> <path>", | ||
Short: "export package to a tar.gz file, path can be a specific filename or a directory.", | ||
Args: cobra.ExactArgs(2), | ||
RunE: export, | ||
} | ||
errPathInvalid = errors.New("path should be an existing directory or a file with .tar.gz/tgz suffix") | ||
) | ||
|
||
func export(cmd *cobra.Command, args []string) error { | ||
name := args[0] | ||
pkgs, err := common.DefaultDB.ListPackages(&name) | ||
if err == gorm.ErrRecordNotFound { | ||
return fmt.Errorf("package %s not found", name) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
targetPackage := pkgs[0] | ||
if targetPackage.State != common.PackageActive { | ||
return fmt.Errorf("package %s is not active, airgap resources maybe missing", name) | ||
} | ||
|
||
path := args[1] | ||
|
||
info, err := os.Lstat(path) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// check file name if path not exist. | ||
if os.IsNotExist(err) { | ||
base := filepath.Base(path) | ||
if !strings.HasSuffix(base, ".tgz") && | ||
!strings.HasSuffix(base, ".tar.gz") { | ||
return errPathInvalid | ||
} | ||
if _, err := os.Lstat(filepath.Dir(path)); err != nil { | ||
return err | ||
} | ||
// here means path is a file with tar.gz/tgz suffix and parent dir exists | ||
} else if !info.IsDir() { | ||
// here means that input path is a regular file and should return error | ||
return errPathInvalid | ||
} else { | ||
// here means that the input path is a dir and will use package name as the output name | ||
path = filepath.Join(path, name+".tar.gz") | ||
} | ||
|
||
if err := pkgairgap.TarAndGzip(targetPackage.FilePath, path); err != nil { | ||
return err | ||
} | ||
cmd.Printf("package %s export to %s succeed\n", name, path) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package airgap | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
|
||
pkgairgap "github.com/cnrancher/autok3s/pkg/airgap" | ||
"github.com/cnrancher/autok3s/pkg/common" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
importCmd = &cobra.Command{ | ||
Use: "import <path> [name]", | ||
Short: "Import an existing tar.gz file of airgap package. Please refer to export command", | ||
Args: func(cmd *cobra.Command, args []string) error { | ||
if err := cobra.MaximumNArgs(2)(cmd, args); err != nil { | ||
return err | ||
} | ||
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil { | ||
return err | ||
} | ||
return nil | ||
}, | ||
RunE: importFunc, | ||
} | ||
errNameRequire = errors.New("name is required for importing a airgap package") | ||
) | ||
|
||
func importFunc(cmd *cobra.Command, args []string) error { | ||
path := args[0] | ||
var name string | ||
var err error | ||
if len(args) < 2 { | ||
name, err = askForName() | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
name = args[1] | ||
} | ||
|
||
if name == "" { | ||
return errNameRequire | ||
} | ||
tmpPath, err := pkgairgap.SaveToTmp(path, name) | ||
if err != nil { | ||
return err | ||
} | ||
defer os.RemoveAll(tmpPath) | ||
|
||
toSave, err := pkgairgap.VerifyFiles(tmpPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
toSave.Name = name | ||
toSave.FilePath = pkgairgap.PackagePath(name) | ||
if err := os.Rename(tmpPath, toSave.FilePath); err != nil { | ||
return err | ||
} | ||
toSave.State = common.PackageActive | ||
if err := common.DefaultDB.SavePackage(*toSave); err != nil { | ||
_ = os.RemoveAll(toSave.FilePath) | ||
return err | ||
} | ||
cmd.Printf("package %s imported from %s\n", name, path) | ||
return nil | ||
} | ||
|
||
func askForName() (string, error) { | ||
rtn := "" | ||
err := survey.AskOne(&survey.Input{ | ||
Message: "Please input the package name", | ||
}, &rtn, survey.WithValidator(func(ans interface{}) error { | ||
name, _ := ans.(string) | ||
return validateName(name) | ||
})) | ||
return rtn, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package airgap | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"strings" | ||
|
||
"github.com/cnrancher/autok3s/pkg/common" | ||
|
||
"github.com/olekukonko/tablewriter" | ||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
listCmd = &cobra.Command{ | ||
Use: "ls", | ||
Short: "List all stored airgap packages.", | ||
RunE: list, | ||
} | ||
) | ||
|
||
func init() { | ||
listCmd.Flags().BoolVarP(&airgapFlags.isJSON, "json", "j", airgapFlags.isJSON, "json output") | ||
} | ||
|
||
func list(cmd *cobra.Command, args []string) error { | ||
pkgs, err := common.DefaultDB.ListPackages(nil) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to list airgap packages.") | ||
} | ||
if airgapFlags.isJSON { | ||
data, err := json.Marshal(pkgs) | ||
if err != nil { | ||
return err | ||
} | ||
cmd.Printf("%s\n", string(data)) | ||
return nil | ||
} | ||
table := tablewriter.NewWriter(os.Stdout) | ||
table.SetBorder(false) | ||
table.SetHeaderLine(false) | ||
table.SetColumnSeparator("") | ||
table.SetAlignment(tablewriter.ALIGN_LEFT) | ||
table.SetHeader([]string{"Name", "K3sVersion", "Archs", "State"}) | ||
for _, pkg := range pkgs { | ||
table.Append([]string{ | ||
pkg.Name, | ||
pkg.K3sVersion, | ||
strings.Join(pkg.Archs, ","), | ||
string(pkg.State), | ||
}) | ||
} | ||
table.Render() | ||
return nil | ||
} |
Oops, something went wrong.