465 lines
14 KiB
Go
465 lines
14 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/Akilan1999/p2p-rendering-computation/client"
|
|
"github.com/Akilan1999/p2p-rendering-computation/config"
|
|
"github.com/google/uuid"
|
|
"gopkg.in/yaml.v2"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"text/template"
|
|
|
|
"github.com/apenella/go-ansible/pkg/execute"
|
|
"github.com/apenella/go-ansible/pkg/options"
|
|
"github.com/apenella/go-ansible/pkg/playbook"
|
|
"github.com/apenella/go-ansible/pkg/stdoutcallback/results"
|
|
"github.com/otiai10/copy"
|
|
)
|
|
|
|
// Plugins Array of all plugins detected
|
|
type Plugins struct {
|
|
PluginsDetected []*Plugin
|
|
}
|
|
|
|
// Plugin Information about the plugins available
|
|
type Plugin struct {
|
|
FolderName string
|
|
PluginDescription string
|
|
path string
|
|
Execute []*ExecuteIP
|
|
NumOfPorts int
|
|
}
|
|
|
|
// ExecuteIP IP Address to execute Ansible instruction
|
|
type ExecuteIP struct {
|
|
ContainerID string
|
|
IPAddress string
|
|
SSHPortNo string
|
|
Success bool
|
|
}
|
|
|
|
// Host Struct for ansible host
|
|
// Generated from https://zhwt.github.io/yaml-to-go/
|
|
type Host struct {
|
|
All struct {
|
|
Vars struct {
|
|
AnsiblePythonInterpreter string `yaml:"ansible_python_interpreter"`
|
|
} `yaml:"vars"`
|
|
} `yaml:"all"`
|
|
Main struct {
|
|
Hosts struct {
|
|
Host1 struct {
|
|
AnsibleHost string `yaml:"ansible_host"`
|
|
AnsiblePort int `yaml:"ansible_port"`
|
|
AnsibleUser string `yaml:"ansible_user"`
|
|
AnsibleSSHPass string `yaml:"ansible_ssh_pass"`
|
|
AnsibleSudoPass string `yaml:"ansible_sudo_pass"`
|
|
} `yaml:"host1"`
|
|
} `yaml:"hosts"`
|
|
} `yaml:"main"`
|
|
}
|
|
|
|
// DetectPlugins Detects all the plugins available
|
|
func DetectPlugins() (*Plugins, error) {
|
|
config, err := config.ConfigInit(nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
folders, err := ioutil.ReadDir(config.PluginPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var plugins *Plugins = new(Plugins)
|
|
|
|
for _, f := range folders {
|
|
if f.IsDir() {
|
|
//Declare variable plugin of type Plugin
|
|
var plugin Plugin
|
|
|
|
// Setting name of folder to plugin
|
|
plugin.FolderName = f.Name()
|
|
// Getting Description from file description.txt
|
|
Description, err := ioutil.ReadFile(config.PluginPath + "/" + plugin.FolderName + "/description.txt")
|
|
// if we os.Open returns an error then handle it
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get Description from description.txt
|
|
plugin.PluginDescription = string(Description)
|
|
// Set plugin path
|
|
plugin.path = config.PluginPath + "/" + plugin.FolderName
|
|
|
|
plugins.PluginsDetected = append(plugins.PluginsDetected, &plugin)
|
|
// Get the number of ports the plugin needs
|
|
err = plugin.NumPorts()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return plugins, nil
|
|
}
|
|
|
|
// SearchPlugin Detects plugin information based on the
|
|
// name provided on the parameter
|
|
func SearchPlugin(pluginname string) (*Plugin, error) {
|
|
plugins, err := DetectPlugins()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// loop ot find the plugin name that matches
|
|
for _, plugin := range plugins.PluginsDetected {
|
|
if pluginname == plugin.FolderName {
|
|
return plugin, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("plugin not detected")
|
|
}
|
|
|
|
// RunPlugin Executes plugins based on the plugin name provided
|
|
func RunPlugin(pluginName string, IPAddresses []*ExecuteIP) (*Plugin, error) {
|
|
plugins, err := DetectPlugins()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Variable to store struct information about the plugin
|
|
var plugindetected *Plugin
|
|
for _, plugin := range plugins.PluginsDetected {
|
|
if plugin.FolderName == pluginName {
|
|
plugindetected = plugin
|
|
plugindetected.Execute = IPAddresses
|
|
// Get Execute plugin path from config file
|
|
config, err := config.ConfigInit(nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plugindetected.path = config.PluginPath
|
|
break
|
|
}
|
|
}
|
|
|
|
if plugindetected == nil {
|
|
return nil, errors.New("Plugin not detected")
|
|
}
|
|
|
|
// Create copy of the plugin the tmp directory
|
|
// To ensure we execute the plugin from there
|
|
err = plugindetected.CopyToTmpPlugin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Executing the plugin
|
|
err = plugindetected.ExecutePlugin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return plugindetected, nil
|
|
}
|
|
|
|
// ExecutePlugin Function to execute plugins that are called
|
|
func (p *Plugin) ExecutePlugin() error {
|
|
|
|
// Run ip address to execute ansible inside
|
|
for _, execute := range p.Execute {
|
|
// Modify ansible hosts before executing
|
|
err := execute.ModifyHost(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// sets the ports to the plugin folder
|
|
err = p.AutoSetPorts(execute.ContainerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = execute.RunAnsible(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If ran successfully then change success flag to true
|
|
execute.Success = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RunAnsible Executes based on credentials on the struct
|
|
func (e *ExecuteIP) RunAnsible(p *Plugin) error {
|
|
ansiblePlaybookConnectionOptions := &options.AnsibleConnectionOptions{
|
|
User: "master",
|
|
}
|
|
|
|
ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
|
|
Inventory: p.path + "/" + p.FolderName + "/hosts",
|
|
ExtraVars: map[string]interface{}{"ansible_ssh_common_args": "-o StrictHostKeyChecking=no"},
|
|
}
|
|
|
|
ansiblePlaybookPrivilegeEscalationOptions := &options.AnsiblePrivilegeEscalationOptions{
|
|
Become: true,
|
|
}
|
|
|
|
playbook := &playbook.AnsiblePlaybookCmd{
|
|
Playbooks: []string{p.path + "/" + p.FolderName + "/site.yml"},
|
|
ConnectionOptions: ansiblePlaybookConnectionOptions,
|
|
PrivilegeEscalationOptions: ansiblePlaybookPrivilegeEscalationOptions,
|
|
Options: ansiblePlaybookOptions,
|
|
Exec: execute.NewDefaultExecute(
|
|
execute.WithTransformers(
|
|
results.Prepend("success"),
|
|
),
|
|
),
|
|
}
|
|
|
|
err := playbook.Run(context.TODO())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ModifyHost adds IP address , port no to the config file
|
|
func (e *ExecuteIP) ModifyHost(p *Plugin) error {
|
|
host, err := ReadHost(p.path + "/" + p.FolderName + "/hosts")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Setting ansible host
|
|
host.Main.Hosts.Host1.AnsibleHost = e.IPAddress
|
|
// Setting SSH port no
|
|
host.Main.Hosts.Host1.AnsiblePort, err = strconv.Atoi(e.SSHPortNo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Setting SSH user name
|
|
host.Main.Hosts.Host1.AnsibleUser = "master"
|
|
// Setting SSH password
|
|
host.Main.Hosts.Host1.AnsibleSSHPass = "password"
|
|
// Setting SSH sudo password
|
|
host.Main.Hosts.Host1.AnsibleSudoPass = "password"
|
|
|
|
// write modified information to the hosts yaml file
|
|
data, err := yaml.Marshal(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ioutil.WriteFile(p.path+"/"+p.FolderName+"/hosts", data, 0777)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadHost Reads host file and adds
|
|
func ReadHost(filename string) (*Host, error) {
|
|
buf, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c := &Host{}
|
|
err = yaml.Unmarshal(buf, c)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("in file %q: %v", filename, err)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// RunPluginContainer Runs ansible plugin based on plugin name and container name which
|
|
// is derived from the tracked containers file
|
|
// We pass in the group ID as a parameter because when we modify the ports taken
|
|
func RunPluginContainer(PluginName string, ContainerID string) error {
|
|
// Gets container information based on container ID
|
|
ContainerInformation, err := client.GetContainerInformation(ContainerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Setting Up IP's for which the plugins will be executed
|
|
var ExecuteIPs []*ExecuteIP
|
|
var ExecuteIP ExecuteIP
|
|
// Getting port no of SSH port
|
|
for _, port := range ContainerInformation.Container.Ports.PortSet {
|
|
if port.PortName == "SSH" {
|
|
ExecuteIP.SSHPortNo = fmt.Sprint(port.ExternalPort)
|
|
break
|
|
}
|
|
}
|
|
// Handle error if SSH port is not provided
|
|
if ExecuteIP.SSHPortNo == "" {
|
|
return errors.New("SSH port not found")
|
|
}
|
|
// Split the port no from ip address since current the IP address
|
|
// field is populated as
|
|
// <ip address>:<port no>
|
|
|
|
host, _, err := net.SplitHostPort(ContainerInformation.IpAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// IP address of the container
|
|
ExecuteIP.IPAddress = host
|
|
// Set container ID to ExecutorIP
|
|
ExecuteIP.ContainerID = ContainerInformation.Id
|
|
// Append IP to list of executor IP
|
|
ExecuteIPs = append(ExecuteIPs, &ExecuteIP)
|
|
// Run plugin to execute plugin
|
|
_, err = RunPlugin(PluginName, ExecuteIPs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckRunPlugin Checks if the ID belongs to the group or container
|
|
// calls the plugin function the appropriate amount of times
|
|
func CheckRunPlugin(PluginName string, ID string) error {
|
|
// Check if the ID belongs to the group or container ID
|
|
id, err := client.CheckID(ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// When the ID belongs to a group
|
|
if id == "group" {
|
|
// gets the group information
|
|
group, err := client.GetGroup(ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Iterate through each container information in the group
|
|
// and run the plugin in each of them
|
|
for _, container := range group.TrackContainerList {
|
|
// runs plugin for each container
|
|
err := RunPluginContainer(PluginName, container.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else { // This means the following ID is a container ID
|
|
err := RunPluginContainer(PluginName, ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyToTmpPlugin This function would ensure that we create a copy of the
|
|
// plugin in the tmp directory, and it would be executed
|
|
// from there. This due to the reason of automating port allocation
|
|
// when running plugins
|
|
func (p *Plugin) CopyToTmpPlugin() error {
|
|
// generate rand to UUID this is debug the ansible file if needed
|
|
id := uuid.New()
|
|
// copies the plugin to the tmp directory
|
|
err := copy.Copy(p.path+"/"+p.FolderName, "/tmp/"+id.String()+"_"+p.FolderName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the plugin execution to the tmp location
|
|
p.path = "/tmp"
|
|
p.FolderName = id.String() + "_" + p.FolderName
|
|
|
|
return nil
|
|
}
|
|
|
|
// AutoSetPorts Automatically maps free ports to site.yml file
|
|
func (p *Plugin) AutoSetPorts(containerID string) error {
|
|
container, err := client.GetContainerInformation(containerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// variable that would have a list of ports
|
|
// to be allocated to the plugin system
|
|
var ports []int
|
|
// Counted that increments when a port is taken
|
|
PortTaken := 0
|
|
// setting all external ports available in an array
|
|
for i, port := range container.Container.Ports.PortSet {
|
|
if port.IsUsed == false {
|
|
// Ensuring we break outside the loop once the ports
|
|
// are set.
|
|
if PortTaken >= p.NumOfPorts {
|
|
break
|
|
}
|
|
// Setting the following port flag to true
|
|
container.Container.Ports.PortSet[i].IsUsed = true
|
|
// Incrementing the variable PortTaken
|
|
PortTaken++
|
|
// Maps to internal since
|
|
// Inside the machine
|
|
// internal port -> (maps) same internal port
|
|
// TURN (i.e FRP) based approach (internal port -> maps to different external port)
|
|
ports = append(ports, port.InternalPort)
|
|
}
|
|
}
|
|
|
|
// parses the site.yml file in the tmp directory
|
|
t, err := template.ParseFiles(p.path + "/" + p.FolderName + "/site.yml")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// opens the output file
|
|
f, err := os.Create(p.path + "/" + p.FolderName + "/site.yml")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// sends the ports to the site.yml file to populate them
|
|
err = t.Execute(f, ports)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Once the following is done set port to taken
|
|
// n tracked container list
|
|
err = container.ModifyContainerInformation()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Once the following is done set port to taken
|
|
// I(Groups)
|
|
err = container.ModifyContainerGroups()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NumPorts Gets the Number the ports the
|
|
// plugin requires
|
|
func (p *Plugin) NumPorts() error {
|
|
jsonFile, err := os.Open(p.path + "/ports.json")
|
|
// if we os.Open returns an error then handle it
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// defer the closing of our jsonFile so that we can parse it later on
|
|
defer jsonFile.Close()
|
|
|
|
// read our opened xmlFile as a byte array.
|
|
byteValue, _ := ioutil.ReadAll(jsonFile)
|
|
|
|
// we unmarshal our byteArray which contains our
|
|
// jsonFile's content into 'users' which we defined above
|
|
json.Unmarshal(byteValue, &p)
|
|
|
|
return nil
|
|
}
|