https://liuming-dev.github.io
helm plugin
source code learningQuick introduce of
helm plugin
A overview of usage by helm plugin -h
:
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin -h
Manage client-side Helm plugins.
Usage:
helm plugin [command]
Available Commands:
install install one or more Helm plugins
list list installed Helm plugins
uninstall uninstall one or more Helm plugins
update update one or more Helm plugins
Flags:
-h, --help help for plugin
......
helm plugin list
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin list -h
list installed Helm plugins
Usage:
helm plugin list [flags]
Aliases:
list, ls
Flags:
-h, --help help for list
......
-----------------------------------------------------------------------------------------------------------------------------------------------
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin list --debug
plugin_list.go:35: [debug] pluginDirs: /Users/apple/Library/helm/plugins
NAME VERSION DESCRIPTION
diff 3.1.1 Preview helm upgrade changes as a diff
monitor 0.4.0 Query at a given interval a Prometheus, ElasticSearch or Sentry instance...
push 0.8.1 Push chart package to ChartMuseum
schema-gen 0.0.2 generate json schema for values yaml
Source code
// plugin_list.go
func newPluginListCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error {
debug("pluginDirs: %s", settings.PluginsDirectory)
// 1. list the plugins in the local plugin directory
plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil {
return err
}
// 2. output plugin information in table format
table := uitable.New()
table.AddRow("NAME", "VERSION", "DESCRIPTION")
for _, p := range plugins {
table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description)
}
fmt.Fprintln(out, table)
return nil
},
}
return cmd
}
1 list the plugins in the local plugin directory
// plugin.go
func FindPlugins(plugdirs string) ([]*Plugin, error) {
found := []*Plugin{}
// Let's get all UNIXy and allow path separators
for _, p := range filepath.SplitList(plugdirs) {
// load plugins in local plugin storage directory
matches, err := LoadAll(p)
if err != nil {
return matches, err
}
found = append(found, matches...)
}
return found, nil
}
// plugin.go
func LoadAll(basedir string) ([]*Plugin, error) {
plugins := []*Plugin{}
// We want basedir/*/plugin.yaml
scanpath := filepath.Join(basedir, "*", pluginFileName)
// get every plugin's plugin.yaml path
matches, err := filepath.Glob(scanpath)
if err != nil {
return plugins, err
}
if matches == nil {
return plugins, nil
}
for _, yaml := range matches {
// retrieve every plugin directory
dir := filepath.Dir(yaml)
// load the plugin's plugin.yaml to get Plugin object
p, err := LoadDir(dir)
if err != nil {
return plugins, err
}
plugins = append(plugins, p)
}
return plugins, nil
}
// plugin.go
func LoadDir(dirname string) (*Plugin, error) {
// read the plugin.yaml file
data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName))
if err != nil {
return nil, err
}
plug := &Plugin{Dir: dirname}
// convert the plugin.yaml to Plugin object
if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
return nil, err
}
return plug, nil
}
helm plugin install
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin install -h
This command allows you to install a plugin from a url to a VCS repo or a local path.
Usage:
helm plugin install [options] <path|url>... [flags]
Aliases:
install, add
Flags:
-h, --help help for install
--version string specify a version constraint. If this is not specified, the latest version is installed
......
-----------------------------------------------------------------------------------------------------------------------------------------------
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin install https://github.com/karuppiah7890/helm-schema-gen --debug
[debug] vcs_installer.go:159: cloning https://github.com/karuppiah7890/helm-schema-gen to /Users/apple/Library/Caches/helm/plugins/https-github.com-karuppiah7890-helm-schema-gen
[debug] vcs_installer.go:91: copying /Users/apple/Library/Caches/helm/plugins/https-github.com-karuppiah7890-helm-schema-gen to /Users/apple/Library/helm/plugins/helm-schema-gen
plugin_install.go:73: [debug] loading plugin from /Users/apple/Library/helm/plugins/helm-schema-gen
plugin.go:60: [debug] running install hook: /bin/sh -c cd $HELM_PLUGIN_DIR; scripts/install_version.sh
karuppiah7890/helm-schema-gen info checking GitHub for tag '0.0.2'
karuppiah7890/helm-schema-gen info found version: 0.0.2 for 0.0.2/Darwin/x86_64
karuppiah7890/helm-schema-gen info installed ./bin/helm-schema-gen
Installed plugin: schema-gen
Source code
// plugin_install.go
func (o *pluginInstallOptions) run(out io.Writer) error {
installer.Debug = settings.Debug
// 1. construct Installer by the given args from command
i, err := installer.NewForSource(o.source, o.version)
if err != nil {
return err
}
// 2. execute the install logic
if err := installer.Install(i); err != nil {
return err
}
debug("loading plugin from %s", i.Path())
// 3. load the installed plugin information
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
// 4. run plugin install hook if install hook is set
if err := runHook(p, plugin.Install); err != nil {
return err
}
fmt.Fprintf(out, "Installed plugin: %s\n", p.Metadata.Name)
return nil
}
1 construct Installer by the given args from command
// installer.go
// instance a concrete Installer by the given plugin address
func NewForSource(source, version string) (Installer, error) {
// Check if source is a local directory
if isLocalReference(source) {
return NewLocalInstaller(source)
} else if isRemoteHTTPArchive(source) {
return NewHTTPInstaller(source)
}
// if source is the git repository, instance a VCSInstaller
return NewVCSInstaller(source, version)
}
2 execute the install logic
// installer.go
func Install(i Installer) error {
// create the plugin base directory
if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil {
return err
}
// check whether plugin directory exists to be sure that plugin is installed or not
if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) {
return errors.New("plugin already exists")
}
// plugin has not been installed, begin to install
return i.Install()
}
// vcs_installer.go
func (i *VCSInstaller) Install() error {
// synchronize git repository to local plugin cache directory
if err := i.sync(i.Repo); err != nil {
return err
}
// check and find the given plugin version
ref, err := i.solveVersion(i.Repo)
if err != nil {
return err
}
if ref != "" {
if err := i.setVersion(i.Repo, ref); err != nil {
return err
}
}
// check whether the cached plugin directory is a valid plugin
if !isPlugin(i.Repo.LocalPath()) {
return ErrMissingMetadata
}
debug("copying %s to %s", i.Repo.LocalPath(), i.Path())
// copy local plugin cache directory file recursively to local plugin directory
return fs.CopyDir(i.Repo.LocalPath(), i.Path())
}
3 load the installed plugin information by the installed directory
// plugin.go
func LoadDir(dirname string) (*Plugin, error) {
// read all data of the installed plugin's plugin.yaml
data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName))
if err != nil {
return nil, err
}
plug := &Plugin{Dir: dirname}
// deserialize to plugin metadata
if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
return nil, err
}
return plug, nil
}
4 run plugin install hook if install hook is set
// plugin.go
func runHook(p *plugin.Plugin, event string) error {
hook := p.Metadata.Hooks[event]
if hook == "" {
return nil
}
prog := exec.Command("sh", "-c", hook)
// TODO make this work on windows
// I think its ... ¯\_(ツ)_/¯
// prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install())
debug("running %s hook: %s", event, prog)
plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
return errors.Errorf("plugin %s hook for %q exited with error", event, p.Metadata.Name)
}
return err
}
return nil
}
helm plugin update
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin update -h
update one or more Helm plugins
Usage:
helm plugin update <plugin>... [flags]
Aliases:
update, up
Flags:
-h, --help help for update
......
-----------------------------------------------------------------------------------------------------------------------------------------
~/Workspace4go/src/github.com/helm/helm/cmd/helm
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin update schema-gen --debug
plugin_update.go:72: [debug] loading installed plugins from /Users/apple/Library/helm/plugins
[debug] vcs_installer.go:97: updating https://github.com/karuppiah7890/helm-schema-gen
plugin_update.go:114: [debug] loading plugin from /Users/apple/Library/helm/plugins/helm-schema-gen
plugin.go:60: [debug] running update hook: /bin/sh -c cd $HELM_PLUGIN_DIR; scripts/install_version.sh
karuppiah7890/helm-schema-gen info checking GitHub for tag '0.0.2'
karuppiah7890/helm-schema-gen info found version: 0.0.2 for 0.0.2/Darwin/x86_64
karuppiah7890/helm-schema-gen info installed ./bin/helm-schema-gen
Updated plugin: schema-gen
Source code
// plugin_update.go
func (o *pluginUpdateOptions) run(out io.Writer) error {
installer.Debug = settings.Debug
debug("loading installed plugins from %s", settings.PluginsDirectory)
// 1. load plugins from local plugin directory
plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil {
return err
}
var errorPlugins []string
for _, name := range o.names {
// 2. iterate plugin names passed in command, and check whether it's the goal to update
if found := findPlugin(plugins, name); found != nil {
// 3. update the found plugin
if err := updatePlugin(found); err != nil {
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err))
} else {
fmt.Fprintf(out, "Updated plugin: %s\n", name)
}
} else {
errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
}
}
if len(errorPlugins) > 0 {
return errors.Errorf(strings.Join(errorPlugins, "\n"))
}
return nil
}
1 load plugins from local plugin directory
source code reading see above.
2 iterate plugin names passed in command, and check whether it’s the goal to update
// plugin_uninstall.go
// find plugin according to plugin name
func findPlugin(plugins []*plugin.Plugin, name string) *plugin.Plugin {
for _, p := range plugins {
if p.Metadata.Name == name {
return p
}
}
return nil
}
3 update the found plugin
// plugin_update.go
func updatePlugin(p *plugin.Plugin) error {
// get the target plugin local directory
exactLocation, err := filepath.EvalSymlinks(p.Dir)
if err != nil {
return err
}
// get the absolute local directory of the target plugin
absExactLocation, err := filepath.Abs(exactLocation)
if err != nil {
return err
}
// 3.1 instance a VCSInstaller object by the given local plugin directory which is actually a git repository
i, err := installer.FindSource(absExactLocation)
if err != nil {
return err
}
// 3.2 update the local plugin directory file via git
if err := installer.Update(i); err != nil {
return err
}
debug("loading plugin from %s", i.Path())
// load the target plugin directory to get Plugin object
updatedPlugin, err := plugin.LoadDir(i.Path())
if err != nil {
return err
}
// run update hook if set
return runHook(updatedPlugin, plugin.Update)
}
3.1 instance a VCSInstaller object by the given local plugin directory which is actually a git repository
// installer.go
func FindSource(location string) (Installer, error) {
// check whether the given plugin directory is a git repository, if yes, return VCSInstaller object
installer, err := existingVCSRepo(location)
if err != nil && err.Error() == "Cannot detect VCS" {
return installer, errors.New("cannot get information about plugin source")
}
return installer, err
}
// vsc_installer.go
func existingVCSRepo(location string) (Installer, error) {
// instance Repo object by the given directory, if not, err will be returned
repo, err := vcs.NewRepo("", location)
if err != nil {
return nil, err
}
// construct a new VCSInstaller object
i := &VCSInstaller{
Repo: repo,
base: newBase(repo.Remote()),
}
return i, nil
}
3.2 update the local plugin directory file via git
// installer.go
func Update(i Installer) error {
// check whether local plugin exists or not according to checking whether plugin directory exists or not
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
return errors.New("plugin does not exist")
}
// update local plugin dirctory file via git
return i.Update()
}
// vsc_installer.go
func (i *VCSInstaller) Update() error {
debug("updating %s", i.Repo.Remote())
// check whether local git repository files are modified or not
if i.Repo.IsDirty() {
return errors.New("plugin repo was modified")
}
// update local git repository files
if err := i.Repo.Update(); err != nil {
return err
}
// check whether the local directory is a valid plugin directory
if !isPlugin(i.Repo.LocalPath()) {
return ErrMissingMetadata
}
return nil
}
helm plugin uninstall
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin uninstall -h
uninstall one or more Helm plugins
Usage:
helm plugin uninstall <plugin>... [flags]
Aliases:
uninstall, rm, remove
Flags:
-h, --help help for uninstall
......
-----------------------------------------------------------------------------------------------------------------------------------------------
~/Workspace4go/src/github.com/helm/helm/cmd/helm(master*) » ./helm plugin uninstall schema-gen --debug
plugin_uninstall.go:70: [debug] loading installed plugins from /Users/apple/Library/helm/plugins
Uninstalled plugin: schema-gen
Source code
// plugin_uninstall.go
func (o *pluginUninstallOptions) run(out io.Writer) error {
debug("loading installed plugins from %s", settings.PluginsDirectory)
// 1. load plugins from local plugin directory
plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil {
return err
}
var errorPlugins []string
for _, name := range o.names {
// 2. iterate plugin names passed in command, and check whether it's the goal to update
if found := findPlugin(plugins, name); found != nil {
// 3. uninstall the found plugin
if err := uninstallPlugin(found); err != nil {
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to uninstall plugin %s, got error (%v)", name, err))
} else {
fmt.Fprintf(out, "Uninstalled plugin: %s\n", name)
}
} else {
errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
}
}
if len(errorPlugins) > 0 {
return errors.Errorf(strings.Join(errorPlugins, "\n"))
}
return nil
}
1 load plugins from local plugin directory
source code reading see above.
2 iterate plugin names passed in command, and check whether it’s the goal to update
source code reading see above.
3 uninstall the found plugin
// plugin_uninstall.go
func uninstallPlugin(p *plugin.Plugin) error {
// delete the plugin directory include the files
if err := os.RemoveAll(p.Dir); err != nil {
return err
}
// run plugin delete type hook if set
return runHook(p, plugin.Delete)
}