Merge pull request #5969 from jtopjian/provider-cobbler
Cobbler Provider
This commit is contained in:
commit
0db3a57d57
|
@ -565,6 +565,10 @@
|
|||
"ImportPath": "github.com/dylanmei/winrmtest",
|
||||
"Rev": "025617847eb2cf9bd1d851bc3b22ed28e6245ce5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatih/structs",
|
||||
"Rev": "73c4e3dc02a78deaba8640d5f3a8c236ec1352bf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fsouza/go-dockerclient",
|
||||
"Rev": "bf97c77db7c945cbcdbf09d56c6f87a66f54537b"
|
||||
|
@ -854,10 +858,19 @@
|
|||
"ImportPath": "github.com/joyent/gosign/auth",
|
||||
"Rev": "a1f3aa7d52213987117e47d721bcc9a499994d5f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jtopjian/cobblerclient",
|
||||
"Comment": "v0.3.0-33-g53d1c0a",
|
||||
"Rev": "53d1c0a0b003aabfa7ecfa848d856606cb481196"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kardianos/osext",
|
||||
"Rev": "29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kolo/xmlrpc",
|
||||
"Rev": "0826b98aaa29c0766956cb40d45cf7482a597671"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/lib/pq",
|
||||
"Comment": "go1.0-cutoff-74-g8ad2b29",
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/builtin/providers/cobbler"
|
||||
"github.com/hashicorp/terraform/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeOpts{
|
||||
ProviderFunc: cobbler.Provider,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# This script assumes Ubuntu 14.04 is being used.
|
||||
# It will create a standard Cobbler environment that can be used for acceptance testing.
|
||||
|
||||
# With this enviornment spun up, the config should be:
|
||||
# COBBLER_URL=http://127.0.0.1:25151
|
||||
# COBBLER_USERNAME=cobbler
|
||||
# COBBLER_PASSWORD=cobbler
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential git mercurial
|
||||
|
||||
cd
|
||||
echo 'export PATH=$PATH:$HOME/terraform:$HOME/go/bin' >> ~/.bashrc
|
||||
export PATH=$PATH:$HOME/terraform:$HOME/go/bin
|
||||
|
||||
sudo wget -O /usr/local/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme
|
||||
sudo chmod +x /usr/local/bin/gimme
|
||||
/usr/local/bin/gimme 1.6 >> ~/.bashrc
|
||||
eval "$(/usr/local/bin/gimme 1.6)"
|
||||
|
||||
mkdir ~/go
|
||||
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
|
||||
echo 'export GO15VENDOREXPERIMENT=1' >> ~/.bashrc
|
||||
export GOPATH=$HOME/go
|
||||
source ~/.bashrc
|
||||
|
||||
go get github.com/tools/godep
|
||||
go get github.com/hashicorp/terraform
|
||||
cd $GOPATH/src/github.com/hashicorp/terraform
|
||||
godep restore
|
||||
|
||||
# Cobbler
|
||||
sudo apt-get install -y cobbler cobbler-web debmirror dnsmasq
|
||||
|
||||
sudo tee /etc/cobbler/modules.conf <<EOF
|
||||
[authentication]
|
||||
module = authn_configfile
|
||||
[authorization]
|
||||
module = authz_allowall
|
||||
[dns]
|
||||
module = manage_dnsmasq
|
||||
[dhcp]
|
||||
module = manage_dnsmasq
|
||||
[tftpd]
|
||||
module = manage_in_tftpd
|
||||
EOF
|
||||
|
||||
sudo tee /etc/cobbler/dnsmasq.template <<EOF
|
||||
dhcp-range = 192.168.255.200,192.168.255.250
|
||||
server = 8.8.8.8
|
||||
read-ethers
|
||||
addn-hosts = /var/lib/cobbler/cobbler_hosts
|
||||
|
||||
dhcp-option=3,\$next_server
|
||||
dhcp-lease-max=1000
|
||||
dhcp-authoritative
|
||||
dhcp-boot=pxelinux.0
|
||||
dhcp-boot=net:normalarch,pxelinux.0
|
||||
dhcp-boot=net:ia64,\$elilo
|
||||
|
||||
\$insert_cobbler_system_definitions
|
||||
EOF
|
||||
|
||||
sudo sed -i -e 's/^manage_dhcp: 0/manage_dhcp: 1/' /etc/cobbler/settings
|
||||
sudo sed -i -e 's/^manage_dns: 0/manage_dns: 1/' /etc/cobbler/settings
|
||||
sudo sed -i -e 's/^next_server:.*/next_server: 127.0.0.1/' /etc/cobbler/settings
|
||||
sudo sed -i -e 's/^server:.*/server: 127.0.0.1/' /etc/cobbler/settings
|
||||
|
||||
# User: cobbler / Pass: cobbler
|
||||
sudo tee /etc/cobbler/users.digest <<EOF
|
||||
cobbler:Cobbler:2d6bae81669d707b72c0bd9806e01f3
|
||||
EOF
|
||||
|
||||
# The stock version of Cobbler in the Ubuntu repository still has the old cobbler homepage URL
|
||||
sudo sed -i -e 's#content_server = "http://www.cobblerd.org/loaders"#content_server = "http://cobbler.github.com/loaders"#' /usr/lib/python2.7/dist-packages/cobbler/action_dlcontent.py
|
||||
sudo rm /usr/lib/python2.7/dist-packages/cobbler/action_dlcontent.pyc
|
||||
|
||||
sudo /etc/init.d/apache2 restart
|
||||
sudo stop cobbler
|
||||
sleep 2
|
||||
sudo start cobbler
|
||||
sleep 10
|
||||
sudo cobbler get-loaders
|
||||
sudo cobbler sync
|
||||
|
||||
# Import an Ubuntu 1404 distro
|
||||
cd /tmp
|
||||
wget http://old-releases.ubuntu.com/releases/14.04.2/ubuntu-14.04-server-amd64.iso
|
||||
sudo mount -o loop ubuntu-14.04-server-amd64.iso /mnt
|
||||
sudo cobbler import --name Ubuntu-14.04 --breed ubuntu --path /mnt
|
|
@ -0,0 +1,74 @@
|
|||
# This will spin up an instance and install cobbler onto it so we can run
|
||||
|
||||
# the Terraform acceptance tests against it.
|
||||
|
||||
module "ami" {
|
||||
source = "github.com/terraform-community-modules/tf_aws_ubuntu_ami/ebs"
|
||||
region = "us-west-2"
|
||||
distribution = "trusty"
|
||||
instance_type = "t2.nano"
|
||||
}
|
||||
|
||||
module "vpc" {
|
||||
source = "github.com/terraform-community-modules/tf_aws_vpc"
|
||||
|
||||
name = "cobbler"
|
||||
cidr = "10.0.0.0/16"
|
||||
private_subnets = "10.0.1.0/24"
|
||||
public_subnets = "10.0.101.0/24"
|
||||
azs = "us-west-2a"
|
||||
}
|
||||
|
||||
resource "aws_key_pair" "cobbler" {
|
||||
key_name = "tf-cobbler-acctests"
|
||||
public_key = "${file("~/.ssh/id_rsa.pub")}"
|
||||
}
|
||||
|
||||
resource "aws_security_group" "cobbler" {
|
||||
vpc_id = "${module.vpc.vpc_id}"
|
||||
|
||||
ingress {
|
||||
protocol = "tcp"
|
||||
from_port = 22
|
||||
to_port = 22
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
egress {
|
||||
protocol = "-1"
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "cobbler" {
|
||||
instance_type = "t2.medium"
|
||||
ami = "${module.ami.ami_id}"
|
||||
subnet_id = "${module.vpc.public_subnets}"
|
||||
key_name = "${aws_key_pair.cobbler.id}"
|
||||
vpc_security_group_ids = ["${aws_security_group.cobbler.id}"]
|
||||
|
||||
root_block_device {
|
||||
volume_type = "gp2"
|
||||
volume_size = 40
|
||||
}
|
||||
|
||||
connection {
|
||||
user = "ubuntu"
|
||||
}
|
||||
|
||||
provisioner "remote-exec" {
|
||||
inline = <<-WAIT
|
||||
while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done
|
||||
WAIT
|
||||
}
|
||||
|
||||
provisioner "remote-exec" {
|
||||
script = "${path.module}/deploy.sh"
|
||||
}
|
||||
}
|
||||
|
||||
output "ssh" {
|
||||
value = "ubuntu@${aws_instance.cobbler.public_ip}"
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
|
||||
cobblerClient cobbler.Client
|
||||
}
|
||||
|
||||
func (c *Config) loadAndValidate() error {
|
||||
config := cobbler.ClientConfig{
|
||||
Url: c.Url,
|
||||
Username: c.Username,
|
||||
Password: c.Password,
|
||||
}
|
||||
|
||||
client := cobbler.NewClient(http.DefaultClient, config)
|
||||
_, err := client.Login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.cobblerClient = client
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func Provider() terraform.ResourceProvider {
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Cobbler URL",
|
||||
DefaultFunc: envDefaultFunc("COBBLER_URL"),
|
||||
},
|
||||
|
||||
"username": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The username for accessing Cobbler.",
|
||||
DefaultFunc: envDefaultFunc("COBBLER_USERNAME"),
|
||||
},
|
||||
|
||||
"password": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The password for accessing Cobbler.",
|
||||
DefaultFunc: envDefaultFunc("COBBLER_PASSWORD"),
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"cobbler_distro": resourceDistro(),
|
||||
"cobbler_kickstart_file": resourceKickstartFile(),
|
||||
"cobbler_profile": resourceProfile(),
|
||||
"cobbler_snippet": resourceSnippet(),
|
||||
"cobbler_system": resourceSystem(),
|
||||
},
|
||||
|
||||
ConfigureFunc: configureProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func configureProvider(d *schema.ResourceData) (interface{}, error) {
|
||||
config := Config{
|
||||
Url: d.Get("url").(string),
|
||||
Username: d.Get("username").(string),
|
||||
Password: d.Get("password").(string),
|
||||
}
|
||||
|
||||
if err := config.loadAndValidate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func envDefaultFunc(k string) schema.SchemaDefaultFunc {
|
||||
return func() (interface{}, error) {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func envDefaultFuncAllowMissing(k string) schema.SchemaDefaultFunc {
|
||||
return func() (interface{}, error) {
|
||||
v := os.Getenv(k)
|
||||
return v, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var testAccCobblerProviders map[string]terraform.ResourceProvider
|
||||
var testAccCobblerProvider *schema.Provider
|
||||
|
||||
func init() {
|
||||
testAccCobblerProvider = Provider().(*schema.Provider)
|
||||
testAccCobblerProviders = map[string]terraform.ResourceProvider{
|
||||
"cobbler": testAccCobblerProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = Provider()
|
||||
}
|
||||
|
||||
func testAccCobblerPreCheck(t *testing.T) {
|
||||
v := os.Getenv("COBBLER_USERNAME")
|
||||
if v == "" {
|
||||
t.Fatal("COBBLER_USERNAME must be set for acceptance tests.")
|
||||
}
|
||||
|
||||
v = os.Getenv("COBBLER_PASSWORD")
|
||||
if v == "" {
|
||||
t.Fatal("COBBLER_PASSWORD must be set for acceptance tests.")
|
||||
}
|
||||
|
||||
v = os.Getenv("COBBLER_URL")
|
||||
if v == "" {
|
||||
t.Fatal("COBBLER_URL must be set for acceptance tests.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func resourceDistro() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceDistroCreate,
|
||||
Read: resourceDistroRead,
|
||||
Update: resourceDistroUpdate,
|
||||
Delete: resourceDistroDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"arch": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"breed": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"boot_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"comment": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"fetchable_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kernel": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"kernel_options": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kernel_options_post": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"initrd": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"mgmt_classes": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"os_version": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"owners": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"redhat_management_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"redhat_management_server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"template_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDistroCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Create a cobblerclient.Distro
|
||||
distro := buildDistro(d, config)
|
||||
|
||||
// Attempte to create the Distro
|
||||
log.Printf("[DEBUG] Cobbler Distro: Create Options: %#v", distro)
|
||||
newDistro, err := config.cobblerClient.CreateDistro(distro)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler Distro: Error Creating: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(newDistro.Name)
|
||||
|
||||
return resourceDistroRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceDistroRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Retrieve the distro from cobbler
|
||||
distro, err := config.cobblerClient.GetDistro(d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler Distro: Error Reading (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// Set all fields
|
||||
d.Set("arch", distro.Arch)
|
||||
d.Set("breed", distro.Breed)
|
||||
d.Set("boot_files", distro.BootFiles)
|
||||
d.Set("comment", distro.Comment)
|
||||
d.Set("fetchable_files", distro.FetchableFiles)
|
||||
d.Set("kernel", distro.Kernel)
|
||||
d.Set("kernel_options", distro.KernelOptions)
|
||||
d.Set("kernel_options_post", distro.KernelOptionsPost)
|
||||
d.Set("initrd", distro.Initrd)
|
||||
d.Set("mgmt_classes", distro.MGMTClasses)
|
||||
d.Set("os_version", distro.OSVersion)
|
||||
d.Set("owners", distro.Owners)
|
||||
d.Set("redhat_management_key", distro.RedHatManagementKey)
|
||||
d.Set("redhat_management_server", distro.RedHatManagementServer)
|
||||
d.Set("template_files", distro.TemplateFiles)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDistroUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// create a cobblerclient.Distro
|
||||
distro := buildDistro(d, config)
|
||||
|
||||
// Attempt to updateh the distro with new information
|
||||
log.Printf("[DEBUG] Cobbler Distro: Updating Distro (%s) with options: %+v", d.Id(), distro)
|
||||
err := config.cobblerClient.UpdateDistro(&distro)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler Distro: Error Updating (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return resourceDistroRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceDistroDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Attempt to delete the distro
|
||||
if err := config.cobblerClient.DeleteDistro(d.Id()); err != nil {
|
||||
return fmt.Errorf("Cobbler Distro: Error Deleting (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildDistro builds a cobbler.Distro from the Terraform attributes
|
||||
func buildDistro(d *schema.ResourceData, meta interface{}) cobbler.Distro {
|
||||
mgmtClasses := []string{}
|
||||
for _, i := range d.Get("mgmt_classes").([]interface{}) {
|
||||
mgmtClasses = append(mgmtClasses, i.(string))
|
||||
}
|
||||
|
||||
owners := []string{}
|
||||
for _, i := range d.Get("owners").([]interface{}) {
|
||||
owners = append(owners, i.(string))
|
||||
}
|
||||
|
||||
distro := cobbler.Distro{
|
||||
Arch: d.Get("arch").(string),
|
||||
Breed: d.Get("breed").(string),
|
||||
BootFiles: d.Get("boot_files").(string),
|
||||
Comment: d.Get("comment").(string),
|
||||
FetchableFiles: d.Get("fetchable_files").(string),
|
||||
Kernel: d.Get("kernel").(string),
|
||||
KernelOptions: d.Get("kernel_options").(string),
|
||||
KernelOptionsPost: d.Get("kernel_options_post").(string),
|
||||
Initrd: d.Get("initrd").(string),
|
||||
MGMTClasses: mgmtClasses,
|
||||
Name: d.Get("name").(string),
|
||||
OSVersion: d.Get("os_version").(string),
|
||||
Owners: owners,
|
||||
RedHatManagementKey: d.Get("redhat_management_key").(string),
|
||||
RedHatManagementServer: d.Get("redhat_management_server").(string),
|
||||
TemplateFiles: d.Get("template_files").(string),
|
||||
}
|
||||
|
||||
return distro
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func TestAccCobblerDistro_basic(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckDistroDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerDistro_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccCobblerDistro_change(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckDistroDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerDistro_change_1,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerDistro_change_2,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCobblerCheckDistroDestroy(s *terraform.State) error {
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "cobbler_distro" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := config.cobblerClient.GetDistro(rs.Primary.ID); err == nil {
|
||||
return fmt.Errorf("Distro still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCobblerCheckDistroExists(t *testing.T, n string, distro *cobbler.Distro) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.cobblerClient.GetDistro(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Distro not found")
|
||||
}
|
||||
|
||||
*distro = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCobblerDistro_basic = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}`
|
||||
|
||||
var testAccCobblerDistro_change_1 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
comment = "I am a distro"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}`
|
||||
|
||||
var testAccCobblerDistro_change_2 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
comment = "I am a distro again"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}`
|
|
@ -0,0 +1,83 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func resourceKickstartFile() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceKickstartFileCreate,
|
||||
Read: resourceKickstartFileRead,
|
||||
Update: resourceKickstartFileUpdate,
|
||||
Delete: resourceKickstartFileDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"body": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceKickstartFileCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
ks := cobbler.KickstartFile{
|
||||
Name: d.Get("name").(string),
|
||||
Body: d.Get("body").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cobbler KickstartFile: Create Options: %#v", ks)
|
||||
|
||||
if err := config.cobblerClient.CreateKickstartFile(ks); err != nil {
|
||||
return fmt.Errorf("Cobbler KickstartFile: Error Creating: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(ks.Name)
|
||||
|
||||
return resourceKickstartFileRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceKickstartFileRead(d *schema.ResourceData, meta interface{}) error {
|
||||
// Since all attributes are required and not computed,
|
||||
// there's no reason to read.
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceKickstartFileUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
ks := cobbler.KickstartFile{
|
||||
Name: d.Id(),
|
||||
Body: d.Get("body").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cobbler KickstartFile: Updating Kickstart (%s) with options: %+v", d.Id(), ks)
|
||||
|
||||
if err := config.cobblerClient.CreateKickstartFile(ks); err != nil {
|
||||
return fmt.Errorf("Cobbler KickstartFile: Error Updating (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return resourceKickstartFileRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceKickstartFileDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
if err := config.cobblerClient.DeleteKickstartFile(d.Id()); err != nil {
|
||||
return fmt.Errorf("Cobbler KickstartFile: Error Deleting (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func TestAccCobblerKickstartFile_basic(t *testing.T) {
|
||||
var ks cobbler.KickstartFile
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckKickstartFileDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerKickstartFile_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckKickstartFileExists(t, "cobbler_kickstart_file.foo", &ks),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCobblerCheckKickstartFileDestroy(s *terraform.State) error {
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "cobbler_kickstart_file" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := config.cobblerClient.GetKickstartFile(rs.Primary.ID); err == nil {
|
||||
return fmt.Errorf("Kickstart File still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCobblerCheckKickstartFileExists(t *testing.T, n string, ks *cobbler.KickstartFile) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.cobblerClient.GetKickstartFile(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Kickstart File not found")
|
||||
}
|
||||
|
||||
*ks = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCobblerKickstartFile_basic = `
|
||||
resource "cobbler_kickstart_file" "foo" {
|
||||
name = "/var/lib/cobbler/kickstarts/foo.ks"
|
||||
body = "I'm a kickstart file."
|
||||
}`
|
|
@ -0,0 +1,369 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func resourceProfile() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceProfileCreate,
|
||||
Read: resourceProfileRead,
|
||||
Update: resourceProfileUpdate,
|
||||
Delete: resourceProfileDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"boot_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"comment": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"distro": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"enable_gpxe": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"enable_menu": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"fetchable_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kernel_options": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kernel_options_post": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kickstart": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ks_meta": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"mgmt_classes": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"mgmt_parameters": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"name_servers_search": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"name_servers": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"owners": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"parent": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"proxy": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"redhat_management_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"redhat_management_server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"repos": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"template_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"template_remote_kickstarts": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_auto_boot": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_bridge": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_cpus": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_disk_driver": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_file_size": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_ram": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceProfileCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Create a cobblerclient.Profile struct
|
||||
profile := buildProfile(d, config)
|
||||
|
||||
// Attempt to create the Profile
|
||||
log.Printf("[DEBUG] Cobbler Profile: Create Options: %#v", profile)
|
||||
newProfile, err := config.cobblerClient.CreateProfile(profile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler Profile: Error Creating: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(newProfile.Name)
|
||||
|
||||
return resourceProfileRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceProfileRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Retrieve the profile entry from Cobbler
|
||||
profile, err := config.cobblerClient.GetProfile(d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler Profile: Error Reading (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// Set all fields
|
||||
d.Set("boot_files", profile.BootFiles)
|
||||
d.Set("comment", profile.Comment)
|
||||
d.Set("distro", profile.Distro)
|
||||
d.Set("enable_gpxe", profile.EnableGPXE)
|
||||
d.Set("enable_menu", profile.EnableMenu)
|
||||
d.Set("fetchable_files", profile.FetchableFiles)
|
||||
d.Set("kernel_options", profile.KernelOptions)
|
||||
d.Set("kernel_options_post", profile.KernelOptionsPost)
|
||||
d.Set("kickstart", profile.Kickstart)
|
||||
d.Set("ks_meta", profile.KSMeta)
|
||||
d.Set("mgmt_classes", profile.MGMTClasses)
|
||||
d.Set("mgmt_parameters", profile.MGMTParameters)
|
||||
d.Set("name_servers_search", profile.NameServersSearch)
|
||||
d.Set("name_servers", profile.NameServers)
|
||||
d.Set("owners", profile.Owners)
|
||||
d.Set("proxy", profile.Proxy)
|
||||
d.Set("redhat_management_key", profile.RedHatManagementKey)
|
||||
d.Set("redhat_management_server", profile.RedHatManagementServer)
|
||||
d.Set("repos", profile.Repos)
|
||||
d.Set("template_files", profile.TemplateFiles)
|
||||
d.Set("template_remote_kickstarts", profile.TemplateRemoteKickstarts)
|
||||
d.Set("virt_auto_boot", profile.VirtAutoBoot)
|
||||
d.Set("virt_bridge", profile.VirtBridge)
|
||||
d.Set("virt_cpus", profile.VirtCPUs)
|
||||
d.Set("virt_disk_driver", profile.VirtDiskDriver)
|
||||
d.Set("virt_file_size", profile.VirtFileSize)
|
||||
d.Set("virt_path", profile.VirtPath)
|
||||
d.Set("virt_ram", profile.VirtRam)
|
||||
d.Set("virt_type", profile.VirtType)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceProfileUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Create a cobblerclient.Profile struct
|
||||
profile := buildProfile(d, config)
|
||||
|
||||
// Attempt to update the profile with new information
|
||||
log.Printf("[DEBUG] Cobbler Profile: Updating Profile (%s) with options: %+v", d.Id(), profile)
|
||||
err := config.cobblerClient.UpdateProfile(&profile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler Profile: Error Updating (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return resourceProfileRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceProfileDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Attempt to delete the profile
|
||||
if err := config.cobblerClient.DeleteProfile(d.Id()); err != nil {
|
||||
return fmt.Errorf("Cobbler Profile: Error Deleting (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildProfile builds a cobblerclient.Profile out of the Terraform attributes
|
||||
func buildProfile(d *schema.ResourceData, meta interface{}) cobbler.Profile {
|
||||
mgmtClasses := []string{}
|
||||
for _, i := range d.Get("mgmt_classes").([]interface{}) {
|
||||
mgmtClasses = append(mgmtClasses, i.(string))
|
||||
}
|
||||
|
||||
nameServersSearch := []string{}
|
||||
for _, i := range d.Get("name_servers_search").([]interface{}) {
|
||||
nameServersSearch = append(nameServersSearch, i.(string))
|
||||
}
|
||||
|
||||
nameServers := []string{}
|
||||
for _, i := range d.Get("name_servers").([]interface{}) {
|
||||
nameServers = append(nameServers, i.(string))
|
||||
}
|
||||
|
||||
owners := []string{}
|
||||
for _, i := range d.Get("owners").([]interface{}) {
|
||||
owners = append(owners, i.(string))
|
||||
}
|
||||
|
||||
repos := []string{}
|
||||
for _, i := range d.Get("repos").([]interface{}) {
|
||||
repos = append(repos, i.(string))
|
||||
}
|
||||
|
||||
profile := cobbler.Profile{
|
||||
BootFiles: d.Get("boot_files").(string),
|
||||
Comment: d.Get("comment").(string),
|
||||
Distro: d.Get("distro").(string),
|
||||
EnableGPXE: d.Get("enable_gpxe").(bool),
|
||||
EnableMenu: d.Get("enable_menu").(bool),
|
||||
FetchableFiles: d.Get("fetchable_files").(string),
|
||||
KernelOptions: d.Get("kernel_options").(string),
|
||||
KernelOptionsPost: d.Get("kernel_options_post").(string),
|
||||
Kickstart: d.Get("kickstart").(string),
|
||||
KSMeta: d.Get("ks_meta").(string),
|
||||
MGMTClasses: mgmtClasses,
|
||||
MGMTParameters: d.Get("mgmt_parameters").(string),
|
||||
Name: d.Get("name").(string),
|
||||
NameServersSearch: nameServersSearch,
|
||||
NameServers: nameServers,
|
||||
Owners: owners,
|
||||
Parent: d.Get("parent").(string),
|
||||
Proxy: d.Get("proxy").(string),
|
||||
RedHatManagementKey: d.Get("redhat_management_key").(string),
|
||||
RedHatManagementServer: d.Get("redhat_management_server").(string),
|
||||
Repos: repos,
|
||||
Server: d.Get("server").(string),
|
||||
TemplateFiles: d.Get("template_files").(string),
|
||||
TemplateRemoteKickstarts: d.Get("template_remote_kickstarts").(int),
|
||||
VirtAutoBoot: d.Get("virt_auto_boot").(string),
|
||||
VirtBridge: d.Get("virt_bridge").(string),
|
||||
VirtCPUs: d.Get("virt_cpus").(string),
|
||||
VirtDiskDriver: d.Get("virt_disk_driver").(string),
|
||||
VirtFileSize: d.Get("virt_file_size").(string),
|
||||
VirtPath: d.Get("virt_path").(string),
|
||||
VirtRam: d.Get("virt_ram").(string),
|
||||
VirtType: d.Get("virt_type").(string),
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func TestAccCobblerProfile_basic(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
var profile cobbler.Profile
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckProfileDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerProfile_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccCobblerProfile_change(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
var profile cobbler.Profile
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckProfileDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerProfile_change_1,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerProfile_change_2,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCobblerCheckProfileDestroy(s *terraform.State) error {
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "cobbler_profile" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := config.cobblerClient.GetProfile(rs.Primary.ID); err == nil {
|
||||
return fmt.Errorf("Profile still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCobblerCheckProfileExists(t *testing.T, n string, profile *cobbler.Profile) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.cobblerClient.GetProfile(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Profile not found")
|
||||
}
|
||||
|
||||
*profile = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCobblerProfile_basic = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}`
|
||||
|
||||
var testAccCobblerProfile_change_1 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
comment = "I am a profile"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}`
|
||||
|
||||
var testAccCobblerProfile_change_2 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
comment = "I am a profile again"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}`
|
|
@ -0,0 +1,83 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func resourceSnippet() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceSnippetCreate,
|
||||
Read: resourceSnippetRead,
|
||||
Update: resourceSnippetUpdate,
|
||||
Delete: resourceSnippetDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"body": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceSnippetCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
snippet := cobbler.Snippet{
|
||||
Name: d.Get("name").(string),
|
||||
Body: d.Get("body").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cobbler Snippet: Create Options: %#v", snippet)
|
||||
|
||||
if err := config.cobblerClient.CreateSnippet(snippet); err != nil {
|
||||
return fmt.Errorf("Cobbler Snippet: Error Creating: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(snippet.Name)
|
||||
|
||||
return resourceSnippetRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceSnippetRead(d *schema.ResourceData, meta interface{}) error {
|
||||
// Since all attributes are required and not computed,
|
||||
// there's no reason to read.
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceSnippetUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
snippet := cobbler.Snippet{
|
||||
Name: d.Id(),
|
||||
Body: d.Get("body").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cobbler Snippet: Updating Snippet (%s) with options: %+v", d.Id(), snippet)
|
||||
|
||||
if err := config.cobblerClient.CreateSnippet(snippet); err != nil {
|
||||
return fmt.Errorf("Cobbler Snippet: Error Updating (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return resourceSnippetRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceSnippetDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
if err := config.cobblerClient.DeleteSnippet(d.Id()); err != nil {
|
||||
return fmt.Errorf("Cobbler Snippet: Error Deleting (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func TestAccCobblerSnippet_basic(t *testing.T) {
|
||||
var snippet cobbler.Snippet
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckSnippetDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSnippet_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckSnippetExists(t, "cobbler_snippet.foo", &snippet),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCobblerCheckSnippetDestroy(s *terraform.State) error {
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "cobbler_snippet" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := config.cobblerClient.GetSnippet(rs.Primary.ID); err == nil {
|
||||
return fmt.Errorf("Snippet still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCobblerCheckSnippetExists(t *testing.T, n string, snippet *cobbler.Snippet) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.cobblerClient.GetSnippet(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Snippet not found")
|
||||
}
|
||||
|
||||
*snippet = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCobblerSnippet_basic = `
|
||||
resource "cobbler_snippet" "foo" {
|
||||
name = "/var/lib/cobbler/snippets/foo"
|
||||
body = "I'm a Snippet."
|
||||
}`
|
|
@ -0,0 +1,851 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/hashcode"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
var systemSyncLock sync.Mutex
|
||||
|
||||
func resourceSystem() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceSystemCreate,
|
||||
Read: resourceSystemRead,
|
||||
Update: resourceSystemUpdate,
|
||||
Delete: resourceSystemDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"boot_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"comment": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"enable_gpxe": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"fetchable_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"hostname": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"image": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"interface": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"cnames": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"dhcp_tag": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"dns_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"bonding_opts": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"bridge_opts": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"interface_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"interface_master": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ip_address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ipv6_address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ipv6_secondaries": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"ipv6_mtu": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ipv6_static_routes": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"ipv6_default_gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"mac_address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"management": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"netmask": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"static": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"static_routes": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"virt_bridge": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: resourceSystemInterfaceHash,
|
||||
},
|
||||
|
||||
"ipv6_default_device": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kernel_options": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kernel_options_post": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"kickstart": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ks_meta": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ldap_enabled": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"ldap_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"mgmt_classes": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"mgmt_parameters": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"monit_enabled": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"name_servers_search": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"name_servers": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"netboot_enabled": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"owners": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"power_address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"power_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"power_pass": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"power_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"power_user": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"profile": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"proxy": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"redhat_management_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"redhat_management_server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"status": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"template_files": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"template_remote_kickstarts": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_auto_boot": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_file_size": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_cpus": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_pxe_boot": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_ram": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"virt_disk_driver": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceSystemCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
systemSyncLock.Lock()
|
||||
defer systemSyncLock.Unlock()
|
||||
|
||||
config := meta.(*Config)
|
||||
|
||||
// Create a cobblerclient.System struct
|
||||
system := buildSystem(d)
|
||||
|
||||
// Attempt to create the System
|
||||
log.Printf("[DEBUG] Cobbler System: Create Options: %#v", system)
|
||||
newSystem, err := config.cobblerClient.CreateSystem(system)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error Creating: %s", err)
|
||||
}
|
||||
|
||||
// Build cobblerclient.Interface structs
|
||||
interfaces := buildSystemInterfaces(d.Get("interface").(*schema.Set))
|
||||
|
||||
// Add each interface to the system
|
||||
for interfaceName, interfaceInfo := range interfaces {
|
||||
log.Printf("[DEBUG] Cobbler System Interface %#v: %#v", interfaceName, interfaceInfo)
|
||||
if err := newSystem.CreateInterface(interfaceName, interfaceInfo); err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error adding Interface %s to %s: %s", interfaceName, newSystem.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cobbler System: Created System: %#v", newSystem)
|
||||
d.SetId(newSystem.Name)
|
||||
|
||||
log.Printf("[DEBUG] Cobbler System: syncing system")
|
||||
if err := config.cobblerClient.Sync(); err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error syncing system: %s", err)
|
||||
}
|
||||
|
||||
return resourceSystemRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceSystemRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Retrieve the system entry from Cobbler
|
||||
system, err := config.cobblerClient.GetSystem(d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error Reading (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// Set all fields
|
||||
d.Set("boot_files", system.BootFiles)
|
||||
d.Set("comment", system.Comment)
|
||||
d.Set("enable_gpxe", system.EnableGPXE)
|
||||
d.Set("fetchable_files", system.FetchableFiles)
|
||||
d.Set("gateway", system.Gateway)
|
||||
d.Set("hostname", system.Hostname)
|
||||
d.Set("image", system.Image)
|
||||
d.Set("ipv6_default_device", system.IPv6DefaultDevice)
|
||||
d.Set("kernel_options", system.KernelOptions)
|
||||
d.Set("kernel_options_post", system.KernelOptionsPost)
|
||||
d.Set("kickstart", system.Kickstart)
|
||||
d.Set("ks_meta", system.KSMeta)
|
||||
d.Set("ldap_enabled", system.LDAPEnabled)
|
||||
d.Set("ldap_type", system.LDAPType)
|
||||
d.Set("mgmt_classes", system.MGMTClasses)
|
||||
d.Set("mgmt_parameters", system.MGMTParameters)
|
||||
d.Set("monit_enabled", system.MonitEnabled)
|
||||
d.Set("name_servers_search", system.NameServersSearch)
|
||||
d.Set("name_servers", system.NameServers)
|
||||
d.Set("netboot_enabled", system.NetbootEnabled)
|
||||
d.Set("owners", system.Owners)
|
||||
d.Set("power_address", system.PowerAddress)
|
||||
d.Set("power_id", system.PowerID)
|
||||
d.Set("power_pass", system.PowerPass)
|
||||
d.Set("power_type", system.PowerType)
|
||||
d.Set("power_user", system.PowerUser)
|
||||
d.Set("profile", system.Profile)
|
||||
d.Set("proxy", system.Proxy)
|
||||
d.Set("redhat_management_key", system.RedHatManagementKey)
|
||||
d.Set("redhat_management_server", system.RedHatManagementServer)
|
||||
d.Set("status", system.Status)
|
||||
d.Set("template_files", system.TemplateFiles)
|
||||
d.Set("template_remote_kickstarts", system.TemplateRemoteKickstarts)
|
||||
d.Set("virt_auto_boot", system.VirtAutoBoot)
|
||||
d.Set("virt_file_size", system.VirtFileSize)
|
||||
d.Set("virt_cpus", system.VirtCPUs)
|
||||
d.Set("virt_type", system.VirtType)
|
||||
d.Set("virt_path", system.VirtPath)
|
||||
d.Set("virt_pxe_boot", system.VirtPXEBoot)
|
||||
d.Set("virt_ram", system.VirtRam)
|
||||
d.Set("virt_disk_driver", system.VirtDiskDriver)
|
||||
|
||||
// Get all interfaces that the System has
|
||||
allInterfaces, err := system.GetInterfaces()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler System %s: Error getting interfaces: %s", system.Name, err)
|
||||
}
|
||||
|
||||
// Build a generic map array with the interface attributes
|
||||
var systemInterfaces []map[string]interface{}
|
||||
for interfaceName, interfaceInfo := range allInterfaces {
|
||||
iface := make(map[string]interface{})
|
||||
iface["name"] = interfaceName
|
||||
iface["cnames"] = interfaceInfo.CNAMEs
|
||||
iface["dhcp_tag"] = interfaceInfo.DHCPTag
|
||||
iface["dns_name"] = interfaceInfo.DNSName
|
||||
iface["bonding_opts"] = interfaceInfo.BondingOpts
|
||||
iface["bridge_opts"] = interfaceInfo.BridgeOpts
|
||||
iface["gateway"] = interfaceInfo.Gateway
|
||||
iface["interface_type"] = interfaceInfo.InterfaceType
|
||||
iface["interface_master"] = interfaceInfo.InterfaceMaster
|
||||
iface["ip_address"] = interfaceInfo.IPAddress
|
||||
iface["ipv6_address"] = interfaceInfo.IPv6Address
|
||||
iface["ipv6_secondaries"] = interfaceInfo.IPv6Secondaries
|
||||
iface["ipv6_mtu"] = interfaceInfo.IPv6MTU
|
||||
iface["ipv6_static_routes"] = interfaceInfo.IPv6StaticRoutes
|
||||
iface["ipv6_default_gateway"] = interfaceInfo.IPv6DefaultGateway
|
||||
iface["mac_address"] = interfaceInfo.MACAddress
|
||||
iface["management"] = interfaceInfo.Management
|
||||
iface["netmask"] = interfaceInfo.Netmask
|
||||
iface["static"] = interfaceInfo.Static
|
||||
iface["static_Routes"] = interfaceInfo.StaticRoutes
|
||||
iface["virt_bridge"] = interfaceInfo.VirtBridge
|
||||
systemInterfaces = append(systemInterfaces, iface)
|
||||
}
|
||||
|
||||
d.Set("interface", systemInterfaces)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceSystemUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
systemSyncLock.Lock()
|
||||
defer systemSyncLock.Unlock()
|
||||
|
||||
config := meta.(*Config)
|
||||
|
||||
// Retrieve the existing system entry from Cobbler
|
||||
system, err := config.cobblerClient.GetSystem(d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error Reading (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// Get a list of the old interfaces
|
||||
currentInterfaces, err := system.GetInterfaces()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting interfaces: %s", err)
|
||||
}
|
||||
log.Printf("[DEBUG] Cobbler System Interfaces: %#v", currentInterfaces)
|
||||
|
||||
// Create a new cobblerclient.System struct with the new information
|
||||
newSystem := buildSystem(d)
|
||||
|
||||
// Attempt to update the system with new information
|
||||
log.Printf("[DEBUG] Cobbler System: Updating System (%s) with options: %+v", d.Id(), system)
|
||||
err = config.cobblerClient.UpdateSystem(&newSystem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error Updating (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
if d.HasChange("interface") {
|
||||
oldInterfaces, newInterfaces := d.GetChange("interface")
|
||||
oldInterfacesSet := oldInterfaces.(*schema.Set)
|
||||
newInterfacesSet := newInterfaces.(*schema.Set)
|
||||
interfacesToRemove := oldInterfacesSet.Difference(newInterfacesSet)
|
||||
|
||||
oldIfaces := buildSystemInterfaces(interfacesToRemove)
|
||||
newIfaces := buildSystemInterfaces(newInterfacesSet)
|
||||
|
||||
for interfaceName, interfaceInfo := range oldIfaces {
|
||||
if _, ok := newIfaces[interfaceName]; !ok {
|
||||
// Interface does not exist in the new set,
|
||||
// so it has been removed from terraform.
|
||||
log.Printf("[DEBUG] Cobbler System: Deleting Interface %#v: %#v", interfaceName, interfaceInfo)
|
||||
|
||||
if err := system.DeleteInterface(interfaceName); err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error deleting Interface %s to %s: %s", interfaceName, system.Name, err)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modify interfaces that have changed
|
||||
for interfaceName, interfaceInfo := range newIfaces {
|
||||
log.Printf("[DEBUG] Cobbler System: New Interface %#v: %#v", interfaceName, interfaceInfo)
|
||||
|
||||
if err := system.CreateInterface(interfaceName, interfaceInfo); err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error adding Interface %s to %s: %s", interfaceName, system.Name, err)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cobbler System: syncing system")
|
||||
if err := config.cobblerClient.Sync(); err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error syncing system: %s", err)
|
||||
}
|
||||
|
||||
return resourceSystemRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceSystemDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
// Attempt to delete the system
|
||||
if err := config.cobblerClient.DeleteSystem(d.Id()); err != nil {
|
||||
return fmt.Errorf("Cobbler System: Error Deleting (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildSystem builds a cobblerclient.System out of the Terraform attributes
|
||||
func buildSystem(d *schema.ResourceData) cobbler.System {
|
||||
mgmtClasses := []string{}
|
||||
for _, i := range d.Get("mgmt_classes").([]interface{}) {
|
||||
mgmtClasses = append(mgmtClasses, i.(string))
|
||||
}
|
||||
|
||||
nameServersSearch := []string{}
|
||||
for _, i := range d.Get("name_servers_search").([]interface{}) {
|
||||
nameServersSearch = append(nameServersSearch, i.(string))
|
||||
}
|
||||
|
||||
nameServers := []string{}
|
||||
for _, i := range d.Get("name_servers").([]interface{}) {
|
||||
nameServers = append(nameServers, i.(string))
|
||||
}
|
||||
|
||||
owners := []string{}
|
||||
for _, i := range d.Get("owners").([]interface{}) {
|
||||
owners = append(owners, i.(string))
|
||||
}
|
||||
|
||||
system := cobbler.System{
|
||||
BootFiles: d.Get("boot_files").(string),
|
||||
Comment: d.Get("comment").(string),
|
||||
EnableGPXE: d.Get("enable_gpxe").(bool),
|
||||
FetchableFiles: d.Get("fetchable_files").(string),
|
||||
Gateway: d.Get("gateway").(string),
|
||||
Hostname: d.Get("hostname").(string),
|
||||
Image: d.Get("image").(string),
|
||||
IPv6DefaultDevice: d.Get("ipv6_default_device").(string),
|
||||
KernelOptions: d.Get("kernel_options").(string),
|
||||
KernelOptionsPost: d.Get("kernel_options_post").(string),
|
||||
Kickstart: d.Get("kickstart").(string),
|
||||
KSMeta: d.Get("ks_meta").(string),
|
||||
LDAPEnabled: d.Get("ldap_enabled").(bool),
|
||||
LDAPType: d.Get("ldap_type").(string),
|
||||
MGMTClasses: mgmtClasses,
|
||||
MGMTParameters: d.Get("mgmt_parameters").(string),
|
||||
MonitEnabled: d.Get("monit_enabled").(bool),
|
||||
Name: d.Get("name").(string),
|
||||
NameServersSearch: nameServersSearch,
|
||||
NameServers: nameServers,
|
||||
NetbootEnabled: d.Get("netboot_enabled").(bool),
|
||||
Owners: owners,
|
||||
PowerAddress: d.Get("power_address").(string),
|
||||
PowerID: d.Get("power_id").(string),
|
||||
PowerPass: d.Get("power_pass").(string),
|
||||
PowerType: d.Get("power_type").(string),
|
||||
PowerUser: d.Get("power_user").(string),
|
||||
Profile: d.Get("profile").(string),
|
||||
Proxy: d.Get("proxy").(string),
|
||||
RedHatManagementKey: d.Get("redhat_management_key").(string),
|
||||
RedHatManagementServer: d.Get("redhat_management_server").(string),
|
||||
Status: d.Get("status").(string),
|
||||
TemplateFiles: d.Get("template_files").(string),
|
||||
TemplateRemoteKickstarts: d.Get("template_remote_kickstarts").(int),
|
||||
VirtAutoBoot: d.Get("virt_auto_boot").(string),
|
||||
VirtFileSize: d.Get("virt_file_size").(string),
|
||||
VirtCPUs: d.Get("virt_cpus").(string),
|
||||
VirtType: d.Get("virt_type").(string),
|
||||
VirtPath: d.Get("virt_path").(string),
|
||||
VirtPXEBoot: d.Get("virt_pxe_boot").(int),
|
||||
VirtRam: d.Get("virt_ram").(string),
|
||||
VirtDiskDriver: d.Get("virt_disk_driver").(string),
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
// buildSystemInterface builds a cobblerclient.Interface out of the Terraform attributes
|
||||
func buildSystemInterfaces(systemInterfaces *schema.Set) cobbler.Interfaces {
|
||||
interfaces := make(cobbler.Interfaces)
|
||||
rawInterfaces := systemInterfaces.List()
|
||||
for _, rawInterface := range rawInterfaces {
|
||||
rawInterfaceMap := rawInterface.(map[string]interface{})
|
||||
|
||||
cnames := []string{}
|
||||
for _, i := range rawInterfaceMap["cnames"].([]interface{}) {
|
||||
cnames = append(cnames, i.(string))
|
||||
}
|
||||
|
||||
ipv6Secondaries := []string{}
|
||||
for _, i := range rawInterfaceMap["ipv6_secondaries"].([]interface{}) {
|
||||
ipv6Secondaries = append(ipv6Secondaries, i.(string))
|
||||
}
|
||||
|
||||
ipv6StaticRoutes := []string{}
|
||||
for _, i := range rawInterfaceMap["ipv6_static_routes"].([]interface{}) {
|
||||
ipv6StaticRoutes = append(ipv6StaticRoutes, i.(string))
|
||||
}
|
||||
|
||||
staticRoutes := []string{}
|
||||
for _, i := range rawInterfaceMap["static_routes"].([]interface{}) {
|
||||
staticRoutes = append(staticRoutes, i.(string))
|
||||
}
|
||||
|
||||
interfaceName := rawInterfaceMap["name"].(string)
|
||||
interfaces[interfaceName] = cobbler.Interface{
|
||||
CNAMEs: cnames,
|
||||
DHCPTag: rawInterfaceMap["dhcp_tag"].(string),
|
||||
DNSName: rawInterfaceMap["dns_name"].(string),
|
||||
BondingOpts: rawInterfaceMap["bonding_opts"].(string),
|
||||
BridgeOpts: rawInterfaceMap["bridge_opts"].(string),
|
||||
Gateway: rawInterfaceMap["gateway"].(string),
|
||||
InterfaceType: rawInterfaceMap["interface_type"].(string),
|
||||
InterfaceMaster: rawInterfaceMap["interface_master"].(string),
|
||||
IPAddress: rawInterfaceMap["ip_address"].(string),
|
||||
IPv6Address: rawInterfaceMap["ipv6_address"].(string),
|
||||
IPv6Secondaries: ipv6Secondaries,
|
||||
IPv6MTU: rawInterfaceMap["ipv6_mtu"].(string),
|
||||
IPv6StaticRoutes: ipv6StaticRoutes,
|
||||
IPv6DefaultGateway: rawInterfaceMap["ipv6_default_gateway"].(string),
|
||||
MACAddress: rawInterfaceMap["mac_address"].(string),
|
||||
Management: rawInterfaceMap["management"].(bool),
|
||||
Netmask: rawInterfaceMap["netmask"].(string),
|
||||
Static: rawInterfaceMap["static"].(bool),
|
||||
StaticRoutes: staticRoutes,
|
||||
VirtBridge: rawInterfaceMap["virt_bridge"].(string),
|
||||
}
|
||||
}
|
||||
|
||||
return interfaces
|
||||
}
|
||||
|
||||
func resourceSystemInterfaceHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s", m["name"].(string)))
|
||||
|
||||
if v, ok := m["cnames"]; ok {
|
||||
for _, x := range v.([]interface{}) {
|
||||
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := m["dhcp_tag"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["dns_name"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["bonding_opts"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["bridge_opts"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["gateway"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["interface_type"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["interface_master"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["ip_address"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["ipv6_address"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["ipv6_secondaries"]; ok {
|
||||
for _, x := range v.([]interface{}) {
|
||||
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := m["ipv6_mtu"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["ipv6_static_routes"]; ok {
|
||||
for _, x := range v.([]interface{}) {
|
||||
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := m["ipv6_default_gateway"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["mac_address"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["management"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(bool)))
|
||||
}
|
||||
|
||||
if v, ok := m["netmask"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["static"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(bool)))
|
||||
}
|
||||
|
||||
if v, ok := m["static_Routes"]; ok {
|
||||
for _, x := range v.([]interface{}) {
|
||||
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := m["virt_bridge"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
|
@ -0,0 +1,380 @@
|
|||
package cobbler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
cobbler "github.com/jtopjian/cobblerclient"
|
||||
)
|
||||
|
||||
func TestAccCobblerSystem_basic(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
var profile cobbler.Profile
|
||||
var system cobbler.System
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckSystemDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSystem_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
testAccCobblerCheckSystemExists(t, "cobbler_system.foo", &system),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccCobblerSystem_multi(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
var profile cobbler.Profile
|
||||
var system cobbler.System
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckSystemDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSystem_multi,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
testAccCobblerCheckSystemExists(t, "cobbler_system.foo.45", &system),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccCobblerSystem_change(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
var profile cobbler.Profile
|
||||
var system cobbler.System
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckSystemDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSystem_change_1,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
testAccCobblerCheckSystemExists(t, "cobbler_system.foo", &system),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSystem_change_2,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
testAccCobblerCheckSystemExists(t, "cobbler_system.foo", &system),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccCobblerSystem_removeInterface(t *testing.T) {
|
||||
var distro cobbler.Distro
|
||||
var profile cobbler.Profile
|
||||
var system cobbler.System
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccCobblerPreCheck(t) },
|
||||
Providers: testAccCobblerProviders,
|
||||
CheckDestroy: testAccCobblerCheckSystemDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSystem_removeInterface_1,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
testAccCobblerCheckSystemExists(t, "cobbler_system.foo", &system),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCobblerSystem_removeInterface_2,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCobblerCheckDistroExists(t, "cobbler_distro.foo", &distro),
|
||||
testAccCobblerCheckProfileExists(t, "cobbler_profile.foo", &profile),
|
||||
testAccCobblerCheckSystemExists(t, "cobbler_system.foo", &system),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCobblerCheckSystemDestroy(s *terraform.State) error {
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "cobbler_system" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := config.cobblerClient.GetSystem(rs.Primary.ID); err == nil {
|
||||
return fmt.Errorf("System still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCobblerCheckSystemExists(t *testing.T, n string, system *cobbler.System) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccCobblerProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.cobblerClient.GetSystem(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("System not found")
|
||||
}
|
||||
|
||||
*system = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCobblerSystem_basic = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}
|
||||
|
||||
resource "cobbler_system" "foo" {
|
||||
name = "foo"
|
||||
profile = "${cobbler_profile.foo.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
comment = "I'm a system"
|
||||
power_id = "foo"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
mac_address = "aa:bb:cc:dd:ee:ff"
|
||||
static = true
|
||||
ip_address = "1.2.3.4"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
interface {
|
||||
name = "eth1"
|
||||
mac_address = "aa:bb:cc:dd:ee:fa"
|
||||
static = true
|
||||
ip_address = "1.2.3.5"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
}`
|
||||
|
||||
var testAccCobblerSystem_multi = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}
|
||||
|
||||
resource "cobbler_system" "foo" {
|
||||
count = 50
|
||||
name = "${format("foo-%d", count.index)}"
|
||||
profile = "${cobbler_profile.foo.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
comment = "I'm a system"
|
||||
power_id = "foo"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
}
|
||||
|
||||
interface {
|
||||
name = "eth1"
|
||||
}
|
||||
}`
|
||||
|
||||
var testAccCobblerSystem_change_1 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}
|
||||
|
||||
resource "cobbler_system" "foo" {
|
||||
name = "foo"
|
||||
profile = "${cobbler_profile.foo.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
comment = "I'm a system"
|
||||
power_id = "foo"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
mac_address = "aa:bb:cc:dd:ee:ff"
|
||||
static = true
|
||||
ip_address = "1.2.3.4"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
interface {
|
||||
name = "eth1"
|
||||
mac_address = "aa:bb:cc:dd:ee:fa"
|
||||
static = true
|
||||
ip_address = "1.2.3.5"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
}`
|
||||
|
||||
var testAccCobblerSystem_change_2 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}
|
||||
|
||||
resource "cobbler_system" "foo" {
|
||||
name = "foo"
|
||||
profile = "${cobbler_profile.foo.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
comment = "I'm a system again"
|
||||
power_id = "foo"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
mac_address = "aa:bb:cc:dd:ee:ff"
|
||||
static = true
|
||||
ip_address = "1.2.3.6"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
interface {
|
||||
name = "eth1"
|
||||
mac_address = "aa:bb:cc:dd:ee:fa"
|
||||
static = true
|
||||
ip_address = "1.2.3.5"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
}`
|
||||
|
||||
var testAccCobblerSystem_removeInterface_1 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}
|
||||
|
||||
resource "cobbler_system" "foo" {
|
||||
name = "foo"
|
||||
profile = "${cobbler_profile.foo.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
power_id = "foo"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
mac_address = "aa:bb:cc:dd:ee:ff"
|
||||
static = true
|
||||
ip_address = "1.2.3.4"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
interface {
|
||||
name = "eth1"
|
||||
mac_address = "aa:bb:cc:dd:ee:fa"
|
||||
static = true
|
||||
ip_address = "1.2.3.5"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
}`
|
||||
|
||||
var testAccCobblerSystem_removeInterface_2 = `
|
||||
resource "cobbler_distro" "foo" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
|
||||
resource "cobbler_profile" "foo" {
|
||||
name = "foo"
|
||||
distro = "${cobbler_distro.foo.name}"
|
||||
}
|
||||
|
||||
resource "cobbler_system" "foo" {
|
||||
name = "foo"
|
||||
profile = "${cobbler_profile.foo.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
power_id = "foo"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
mac_address = "aa:bb:cc:dd:ee:ff"
|
||||
static = true
|
||||
ip_address = "1.2.3.4"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
}`
|
|
@ -0,0 +1,23 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- tip
|
||||
sudo: false
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Fatih Arslan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,163 @@
|
|||
# Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](https://coveralls.io/r/fatih/structs)
|
||||
|
||||
Structs contains various utilities to work with Go (Golang) structs. It was
|
||||
initially used by me to convert a struct into a `map[string]interface{}`. With
|
||||
time I've added other utilities for structs. It's basically a high level
|
||||
package based on primitives from the reflect package. Feel free to add new
|
||||
functions or improve the existing code.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/structs
|
||||
```
|
||||
|
||||
## Usage and Examples
|
||||
|
||||
Just like the standard lib `strings`, `bytes` and co packages, `structs` has
|
||||
many global functions to manipulate or organize your struct data. Lets define
|
||||
and declare a struct:
|
||||
|
||||
```go
|
||||
type Server struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ID int
|
||||
Enabled bool
|
||||
users []string // not exported
|
||||
http.Server // embedded
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Name: "gopher",
|
||||
ID: 123456,
|
||||
Enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Convert a struct to a map[string]interface{}
|
||||
// => {"Name":"gopher", "ID":123456, "Enabled":true}
|
||||
m := structs.Map(server)
|
||||
|
||||
// Convert the values of a struct to a []interface{}
|
||||
// => ["gopher", 123456, true]
|
||||
v := structs.Values(server)
|
||||
|
||||
// Convert the names of a struct to a []string
|
||||
// (see "Names methods" for more info about fields)
|
||||
n := structs.Names(server)
|
||||
|
||||
// Convert the values of a struct to a []*Field
|
||||
// (see "Field methods" for more info about fields)
|
||||
f := structs.Fields(server)
|
||||
|
||||
// Return the struct name => "Server"
|
||||
n := structs.Name(server)
|
||||
|
||||
// Check if any field of a struct is initialized or not.
|
||||
h := structs.HasZero(server)
|
||||
|
||||
// Check if all fields of a struct is initialized or not.
|
||||
z := structs.IsZero(server)
|
||||
|
||||
// Check if server is a struct or a pointer to struct
|
||||
i := structs.IsStruct(server)
|
||||
```
|
||||
|
||||
### Struct methods
|
||||
|
||||
The structs functions can be also used as independent methods by creating a new
|
||||
`*structs.Struct`. This is handy if you want to have more control over the
|
||||
structs (such as retrieving a single Field).
|
||||
|
||||
```go
|
||||
// Create a new struct type:
|
||||
s := structs.New(server)
|
||||
|
||||
m := s.Map() // Get a map[string]interface{}
|
||||
v := s.Values() // Get a []interface{}
|
||||
f := s.Fields() // Get a []*Field
|
||||
n := s.Names() // Get a []string
|
||||
f := s.Field(name) // Get a *Field based on the given field name
|
||||
f, ok := s.FieldOk(name) // Get a *Field based on the given field name
|
||||
n := s.Name() // Get the struct name
|
||||
h := s.HasZero() // Check if any field is initialized
|
||||
z := s.IsZero() // Check if all fields are initialized
|
||||
```
|
||||
|
||||
### Field methods
|
||||
|
||||
We can easily examine a single Field for more detail. Below you can see how we
|
||||
get and interact with various field methods:
|
||||
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
// Get the Field struct for the "Name" field
|
||||
name := s.Field("Name")
|
||||
|
||||
// Get the underlying value, value => "gopher"
|
||||
value := name.Value().(string)
|
||||
|
||||
// Set the field's value
|
||||
name.Set("another gopher")
|
||||
|
||||
// Get the field's kind, kind => "string"
|
||||
name.Kind()
|
||||
|
||||
// Check if the field is exported or not
|
||||
if name.IsExported() {
|
||||
fmt.Println("Name field is exported")
|
||||
}
|
||||
|
||||
// Check if the value is a zero value, such as "" for string, 0 for int
|
||||
if !name.IsZero() {
|
||||
fmt.Println("Name is initialized")
|
||||
}
|
||||
|
||||
// Check if the field is an anonymous (embedded) field
|
||||
if !name.IsEmbedded() {
|
||||
fmt.Println("Name is not an embedded field")
|
||||
}
|
||||
|
||||
// Get the Field's tag value for tag name "json", tag value => "name,omitempty"
|
||||
tagValue := name.Tag("json")
|
||||
```
|
||||
|
||||
Nested structs are supported too:
|
||||
|
||||
```go
|
||||
addrField := s.Field("Server").Field("Addr")
|
||||
|
||||
// Get the value for addr
|
||||
a := addrField.Value().(string)
|
||||
|
||||
// Or get all fields
|
||||
httpServer := s.Field("Server").Fields()
|
||||
```
|
||||
|
||||
We can also get a slice of Fields from the Struct type to iterate over all
|
||||
fields. This is handy if you wish to examine all fields:
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
for _, f := range s.Fields() {
|
||||
fmt.Printf("field name: %+v\n", f.Name())
|
||||
|
||||
if f.IsExported() {
|
||||
fmt.Printf("value : %+v\n", f.Value())
|
||||
fmt.Printf("is zero : %+v\n", f.IsZero())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* [Cihangir Savas](https://github.com/cihangir)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see LICENSE.md for more details
|
|
@ -0,0 +1,133 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotExported = errors.New("field is not exported")
|
||||
errNotSettable = errors.New("field is not settable")
|
||||
)
|
||||
|
||||
// Field represents a single struct field that encapsulates high level
|
||||
// functions around the field.
|
||||
type Field struct {
|
||||
value reflect.Value
|
||||
field reflect.StructField
|
||||
defaultTag string
|
||||
}
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
return f.field.Tag.Get(key)
|
||||
}
|
||||
|
||||
// Value returns the underlying value of of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} {
|
||||
return f.value.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return f.field.Anonymous
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.PkgPath == ""
|
||||
}
|
||||
|
||||
// IsZero returns true if the given field is not initialized (has a zero value).
|
||||
// It panics if the field is not exported.
|
||||
func (f *Field) IsZero() bool {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
current := f.Value()
|
||||
|
||||
return reflect.DeepEqual(current, zero)
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
|
||||
func (f *Field) Kind() reflect.Kind {
|
||||
return f.value.Kind()
|
||||
}
|
||||
|
||||
// Set sets the field to given value v. It returns an error if the field is not
|
||||
// settable (not addressable or not exported) or if the given value's type
|
||||
// doesn't match the fields type.
|
||||
func (f *Field) Set(val interface{}) error {
|
||||
// we can't set unexported fields, so be sure this field is exported
|
||||
if !f.IsExported() {
|
||||
return errNotExported
|
||||
}
|
||||
|
||||
// do we get here? not sure...
|
||||
if !f.value.CanSet() {
|
||||
return errNotSettable
|
||||
}
|
||||
|
||||
given := reflect.ValueOf(val)
|
||||
|
||||
if f.value.Kind() != given.Kind() {
|
||||
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
|
||||
}
|
||||
|
||||
f.value.Set(given)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zero sets the field to its zero value. It returns an error if the field is not
|
||||
// settable (not addressable or not exported).
|
||||
func (f *Field) Zero() error {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
return f.Set(zero)
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. This is particular handy to get the fields
|
||||
// of a nested struct . A struct tag with the content of "-" ignores the
|
||||
// checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field *http.Request `structs:"-"`
|
||||
//
|
||||
// It panics if field is not exported or if field's kind is not struct
|
||||
func (f *Field) Fields() []*Field {
|
||||
return getFields(f.value, f.defaultTag)
|
||||
}
|
||||
|
||||
// Field returns the field from a nested struct. It panics if the nested struct
|
||||
// is not exported or if the field was not found.
|
||||
func (f *Field) Field(name string) *Field {
|
||||
field, ok := f.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// Field returns the field from a nested struct. The boolean returns true if
|
||||
// the field was found. It panics if the nested struct is not exported or if
|
||||
// the field was not found.
|
||||
func (f *Field) FieldOk(name string) (*Field, bool) {
|
||||
v := strctVal(f.value.Interface())
|
||||
t := v.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(name),
|
||||
}, true
|
||||
}
|
|
@ -0,0 +1,494 @@
|
|||
// Package structs contains various utilities functions to work with structs.
|
||||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTagName is the default tag name for struct fields which provides
|
||||
// a more granular to tweak certain structs. Lookup the necessary functions
|
||||
// for more info.
|
||||
DefaultTagName = "structs" // struct's field default tag name
|
||||
)
|
||||
|
||||
// Struct encapsulates a struct type to provide several high level functions
|
||||
// around the struct.
|
||||
type Struct struct {
|
||||
raw interface{}
|
||||
value reflect.Value
|
||||
TagName string
|
||||
}
|
||||
|
||||
// New returns a new *Struct with the struct s. It panics if the s's kind is
|
||||
// not struct.
|
||||
func New(s interface{}) *Struct {
|
||||
return &Struct{
|
||||
raw: s,
|
||||
value: strctVal(s),
|
||||
TagName: DefaultTagName,
|
||||
}
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}, where the keys
|
||||
// of the map are the field names and the values of the map the associated
|
||||
// values of the fields. The default key string is the struct field name but
|
||||
// can be changed in the struct field's tag value. The "structs" key in the
|
||||
// struct's field tag value is the key name. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName".
|
||||
// Name string `structs:"myName"`
|
||||
//
|
||||
// A tag value with the content of "-" ignores that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A tag value with the content of "string" uses the stringer to get the value. Example:
|
||||
//
|
||||
// // The value will be output of Animal's String() func.
|
||||
// // Map will panic if Animal does not implement String().
|
||||
// Field *Animal `structs:"field,string"`
|
||||
//
|
||||
// A tag value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field if
|
||||
// the field value is empty. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName", but the field is
|
||||
// // skipped if empty.
|
||||
// Field string `structs:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in map as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Map() map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
s.FillMap(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||
// given map.
|
||||
func (s *Struct) FillMap(out map[string]interface{}) {
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
name := field.Name
|
||||
val := s.value.FieldByName(name)
|
||||
|
||||
var finalVal interface{}
|
||||
|
||||
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
if tagName != "" {
|
||||
name = tagName
|
||||
}
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// map[string]interface{} too
|
||||
n := New(val.Interface())
|
||||
n.TagName = s.TagName
|
||||
m := n.Map()
|
||||
if len(m) == 0 {
|
||||
finalVal = val.Interface()
|
||||
} else {
|
||||
finalVal = m
|
||||
}
|
||||
} else {
|
||||
finalVal = val.Interface()
|
||||
}
|
||||
|
||||
if tagOpts.Has("string") {
|
||||
s, ok := val.Interface().(fmt.Stringer)
|
||||
if ok {
|
||||
out[name] = s.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
out[name] = finalVal
|
||||
}
|
||||
}
|
||||
|
||||
// Values converts the given s struct's field values to a []interface{}. A
|
||||
// struct tag with the content of "-" ignores the that particular field.
|
||||
// Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Fields is not processed further by this package.
|
||||
// Field time.Time `structs:",omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field and
|
||||
// is not added to the values if the field value is empty. Example:
|
||||
//
|
||||
// // Field is skipped if empty
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Values() []interface{} {
|
||||
fields := s.structFields()
|
||||
|
||||
var t []interface{}
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if tagOpts.Has("string") {
|
||||
s, ok := val.Interface().(fmt.Stringer)
|
||||
if ok {
|
||||
t = append(t, s.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// []interface{} to be added to the final values slice
|
||||
for _, embeddedVal := range Values(val.Interface()) {
|
||||
t = append(t, embeddedVal)
|
||||
}
|
||||
} else {
|
||||
t = append(t, val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Fields() []*Field {
|
||||
return getFields(s.value, s.TagName)
|
||||
}
|
||||
|
||||
// Names returns a slice of field names. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Names() []string {
|
||||
fields := getFields(s.value, s.TagName)
|
||||
|
||||
names := make([]string, len(fields))
|
||||
|
||||
for i, field := range fields {
|
||||
names[i] = field.Name()
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func getFields(v reflect.Value, tagName string) []*Field {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
var fields []*Field
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
if tag := field.Tag.Get(tagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f := &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(field.Name),
|
||||
}
|
||||
|
||||
fields = append(fields, f)
|
||||
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. It panics if the field is not found.
|
||||
func (s *Struct) Field(name string) *Field {
|
||||
f, ok := s.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. The boolean returns true if the field
|
||||
// was found.
|
||||
func (s *Struct) FieldOk(name string) (*Field, bool) {
|
||||
t := s.value.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: s.value.FieldByName(name),
|
||||
defaultTag: s.TagName,
|
||||
}, true
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields in a struct is a zero value (not
|
||||
// initialized) A struct tag with the content of "-" ignores the checking of
|
||||
// that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) IsZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := IsZero(val.Interface())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if !reflect.DeepEqual(current, zero) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HasZero returns true if a field in a struct is not initialized (zero value).
|
||||
// A struct tag with the content of "-" ignores the checking of that particular
|
||||
// field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) HasZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := HasZero(val.Interface())
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. For more info refer
|
||||
// to Name() function.
|
||||
func (s *Struct) Name() string {
|
||||
return s.value.Type().Name()
|
||||
}
|
||||
|
||||
// structFields returns the exported struct fields for a given s struct. This
|
||||
// is a convenient helper method to avoid duplicate code in some of the
|
||||
// functions.
|
||||
func (s *Struct) structFields() []reflect.StructField {
|
||||
t := s.value.Type()
|
||||
|
||||
var f []reflect.StructField
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
// we can't access the value of unexported fields
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// don't check if it's omitted
|
||||
if tag := field.Tag.Get(s.TagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f = append(f, field)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func strctVal(s interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(s)
|
||||
|
||||
// if pointer get the underlying element≤
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
panic("not struct")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}. For more info
|
||||
// refer to Struct types Map() method. It panics if s's kind is not struct.
|
||||
func Map(s interface{}) map[string]interface{} {
|
||||
return New(s).Map()
|
||||
}
|
||||
|
||||
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||
// given map.
|
||||
func FillMap(s interface{}, out map[string]interface{}) {
|
||||
New(s).FillMap(out)
|
||||
}
|
||||
|
||||
// Values converts the given struct to a []interface{}. For more info refer to
|
||||
// Struct types Values() method. It panics if s's kind is not struct.
|
||||
func Values(s interface{}) []interface{} {
|
||||
return New(s).Values()
|
||||
}
|
||||
|
||||
// Fields returns a slice of *Field. For more info refer to Struct types
|
||||
// Fields() method. It panics if s's kind is not struct.
|
||||
func Fields(s interface{}) []*Field {
|
||||
return New(s).Fields()
|
||||
}
|
||||
|
||||
// Names returns a slice of field names. For more info refer to Struct types
|
||||
// Names() method. It panics if s's kind is not struct.
|
||||
func Names(s interface{}) []string {
|
||||
return New(s).Names()
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields is equal to a zero value. For more info
|
||||
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
|
||||
func IsZero(s interface{}) bool {
|
||||
return New(s).IsZero()
|
||||
}
|
||||
|
||||
// HasZero returns true if any field is equal to a zero value. For more info
|
||||
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
|
||||
func HasZero(s interface{}) bool {
|
||||
return New(s).HasZero()
|
||||
}
|
||||
|
||||
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||
// struct.
|
||||
func IsStruct(s interface{}) bool {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// uninitialized zero value of a struct
|
||||
if v.Kind() == reflect.Invalid {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. It returns an
|
||||
// empty string for unnamed types. It panics if s's kind is not struct.
|
||||
func Name(s interface{}) string {
|
||||
return New(s).Name()
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package structs
|
||||
|
||||
import "strings"
|
||||
|
||||
// tagOptions contains a slice of tag options
|
||||
type tagOptions []string
|
||||
|
||||
// Has returns true if the given optiton is available in tagOptions
|
||||
func (t tagOptions) Has(opt string) bool {
|
||||
for _, tagOpt := range t {
|
||||
if tagOpt == opt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseTag splits a struct field's tag into its name and a list of options
|
||||
// which comes after a name. A tag is in the form of: "name,option1,option2".
|
||||
// The name can be neglectected.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
// tag is one of followings:
|
||||
// ""
|
||||
// "name"
|
||||
// "name,opt"
|
||||
// "name,opt,opt2"
|
||||
// ",opt"
|
||||
|
||||
res := strings.Split(tag, ",")
|
||||
return res[0], res[1:]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
**/*.swp
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright 2015 Container Solutions
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
build:
|
||||
@go build .
|
||||
|
||||
test:
|
||||
@go test -v .
|
|
@ -0,0 +1,2 @@
|
|||
# cobblerclient
|
||||
Cobbler Client written in Go
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
Copyright 2015 Container Solutions
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cobblerclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kolo/xmlrpc"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const bodyTypeXML = "text/xml"
|
||||
|
||||
type HTTPClient interface {
|
||||
Post(string, string, io.Reader) (*http.Response, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
httpClient HTTPClient
|
||||
config ClientConfig
|
||||
Token string
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewClient(httpClient HTTPClient, c ClientConfig) Client {
|
||||
return Client{
|
||||
httpClient: httpClient,
|
||||
config: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(method string, args ...interface{}) (interface{}, error) {
|
||||
var result interface{}
|
||||
|
||||
reqBody, err := xmlrpc.EncodeMethodCall(method, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := fmt.Sprintf("%s\n", string(reqBody))
|
||||
res, err := c.httpClient.Post(c.config.Url, bodyTypeXML, bytes.NewReader([]byte(r)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := xmlrpc.NewResponse(body)
|
||||
if err := resp.Unmarshal(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Failed() {
|
||||
return nil, resp.Err()
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Performs a login request to Cobbler using the credentials provided
|
||||
// in the configuration in the initializer.
|
||||
func (c *Client) Login() (bool, error) {
|
||||
result, err := c.Call("login", c.config.Username, c.config.Password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
c.Token = result.(string)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Sync the system.
|
||||
// Returns an error if anything went wrong
|
||||
func (c *Client) Sync() error {
|
||||
_, err := c.Call("sync", c.Token)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetItemHandle gets the internal ID of a Cobbler item.
|
||||
func (c *Client) GetItemHandle(what, name string) (string, error) {
|
||||
result, err := c.Call("get_item_handle", what, name, c.Token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return result.(string), err
|
||||
}
|
||||
}
|
||||
|
||||
// cobblerDataHacks is a hook for the mapstructure decoder. It's only used by
|
||||
// decodeCobblerItem and should never be invoked directly.
|
||||
// It's used to smooth out issues with converting fields and types from Cobbler.
|
||||
func cobblerDataHacks(f, t reflect.Kind, data interface{}) (interface{}, error) {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
|
||||
// Cobbler uses ~ internally to mean None/nil
|
||||
if dataVal.String() == "~" {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
if f == reflect.Int64 && t == reflect.Bool {
|
||||
if dataVal.Int() > 0 {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// decodeCobblerItem is a custom mapstructure decoder to handler Cobbler's uniqueness.
|
||||
func decodeCobblerItem(raw interface{}, result interface{}) (interface{}, error) {
|
||||
var metadata mapstructure.Metadata
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Metadata: &metadata,
|
||||
Result: result,
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: cobblerDataHacks,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := decoder.Decode(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// updateCobblerFields updates all fields in a Cobbler Item structure.
|
||||
func (c *Client) updateCobblerFields(what string, item reflect.Value, id string) error {
|
||||
method := fmt.Sprintf("modify_%s", what)
|
||||
|
||||
typeOfT := item.Type()
|
||||
for i := 0; i < item.NumField(); i++ {
|
||||
v := item.Field(i)
|
||||
tag := typeOfT.Field(i).Tag
|
||||
field := tag.Get("mapstructure")
|
||||
cobblerTag := tag.Get("cobbler")
|
||||
|
||||
if cobblerTag == "noupdate" {
|
||||
continue
|
||||
}
|
||||
|
||||
if field == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
switch v.Type().String() {
|
||||
case "string", "bool", "int64", "int":
|
||||
value = v.Interface()
|
||||
case "[]string":
|
||||
value = strings.Join(v.Interface().([]string), " ")
|
||||
}
|
||||
|
||||
//fmt.Printf("%s, %s, %s\n", id, field, value)
|
||||
if result, err := c.Call(method, id, field, value, c.Token); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if result.(bool) == false && value != false {
|
||||
return fmt.Errorf("Error updating %s to %s.", field, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
Copyright 2015 Container Solutions
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cobblerclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Distro is a created distro.
|
||||
type Distro struct {
|
||||
// These are internal fields and cannot be modified.
|
||||
Ctime float64 `mapstructure:"ctime" cobbler:"noupdate"` // TODO: convert to time
|
||||
Depth int `mapstructure:"depth" cobbler:"noupdate"`
|
||||
ID string `mapstructure:"uid" cobbler:"noupdate"`
|
||||
Mtime float64 `mapstructure:"mtime" cobbler:"noupdate"` // TODO: convert to time
|
||||
TreeBuildTime string `mapstructure:tree_build_time" cobbler:"noupdate"`
|
||||
|
||||
Arch string `mapstructure:"arch"`
|
||||
Breed string `mapstructure:"breed"`
|
||||
BootFiles string `mapstructure:"boot_files"`
|
||||
Comment string `mapstructure:"comment"`
|
||||
FetchableFiles string `mapstructure:"fetchable_files"`
|
||||
Kernel string `mapstructure:"kernel"`
|
||||
KernelOptions string `mapstructure:"kernel_options"`
|
||||
KernelOptionsPost string `mapstructure:"kernel_options_post"`
|
||||
Initrd string `mapstructure:"initrd"`
|
||||
MGMTClasses []string `mapstructure:"mgmt_classes"`
|
||||
Name string `mapstructure:"name"`
|
||||
OSVersion string `mapstructure:"os_version"`
|
||||
Owners []string `mapstructure:"owners"`
|
||||
RedHatManagementKey string `mapstructure:"redhat_management_key"`
|
||||
RedHatManagementServer string `mapstructure:"redhat_management_server"`
|
||||
TemplateFiles string `mapstructure:"template_files"`
|
||||
|
||||
//KSMeta string `mapstructure:"ks_meta"`
|
||||
//SourceRepos []string `mapstructure:"source_repos"`
|
||||
}
|
||||
|
||||
// GetDistros returns all systems in Cobbler.
|
||||
func (c *Client) GetDistros() ([]*Distro, error) {
|
||||
var distros []*Distro
|
||||
|
||||
result, err := c.Call("get_distros", "-1", c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range result.([]interface{}) {
|
||||
var distro Distro
|
||||
decodedResult, err := decodeCobblerItem(d, &distro)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
distros = append(distros, decodedResult.(*Distro))
|
||||
}
|
||||
|
||||
return distros, nil
|
||||
}
|
||||
|
||||
// GetDistro returns a single distro obtained by its name.
|
||||
func (c *Client) GetDistro(name string) (*Distro, error) {
|
||||
var distro Distro
|
||||
|
||||
result, err := c.Call("get_distro", name, c.Token)
|
||||
if result == "~" {
|
||||
return nil, fmt.Errorf("Distro %s not found.", name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodeResult, err := decodeCobblerItem(result, &distro)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeResult.(*Distro), nil
|
||||
}
|
||||
|
||||
// CreateDistro creates a distro.
|
||||
func (c *Client) CreateDistro(distro Distro) (*Distro, error) {
|
||||
// Make sure a distro with the same name does not already exist
|
||||
if _, err := c.GetDistro(distro.Name); err == nil {
|
||||
return nil, fmt.Errorf("A Distro with the name %s already exists.", distro.Name)
|
||||
}
|
||||
|
||||
result, err := c.Call("new_distro", c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newId := result.(string)
|
||||
|
||||
item := reflect.ValueOf(&distro).Elem()
|
||||
if err := c.updateCobblerFields("distro", item, newId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := c.Call("save_distro", newId, c.Token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.GetDistro(distro.Name)
|
||||
}
|
||||
|
||||
// UpdateDistro updates a single distro.
|
||||
func (c *Client) UpdateDistro(distro *Distro) error {
|
||||
item := reflect.ValueOf(distro).Elem()
|
||||
id, err := c.GetItemHandle("distro", distro.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.updateCobblerFields("distro", item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := c.Call("save_distro", id, c.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDistro deletes a single distro by its name.
|
||||
func (c *Client) DeleteDistro(name string) error {
|
||||
_, err := c.Call("remove_distro", name, c.Token)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2015 Container Solutions
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cobblerclient
|
||||
|
||||
type KickstartFile struct {
|
||||
Name string // The name the kickstart file will be saved in Cobbler
|
||||
Body string // The contents of the kickstart file
|
||||
}
|
||||
|
||||
// Creates a kickstart file in Cobbler.
|
||||
// Takes a KickstartFile struct as input.
|
||||
// Returns true/false and error if creation failed.
|
||||
func (c *Client) CreateKickstartFile(f KickstartFile) error {
|
||||
_, err := c.Call("read_or_write_kickstart_template", f.Name, false, f.Body, c.Token)
|
||||
return err
|
||||
}
|
||||
|
||||
// Gets a kickstart file in Cobbler.
|
||||
// Takes a kickstart file name as input.
|
||||
// Returns *KickstartFile and error if read failed.
|
||||
func (c *Client) GetKickstartFile(ksName string) (*KickstartFile, error) {
|
||||
result, err := c.Call("read_or_write_kickstart_template", ksName, true, "", c.Token)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ks := KickstartFile{
|
||||
Name: ksName,
|
||||
Body: result.(string),
|
||||
}
|
||||
|
||||
return &ks, nil
|
||||
}
|
||||
|
||||
// Deletes a kickstart file in Cobbler.
|
||||
// Takes a kickstart file name as input.
|
||||
// Returns error if delete failed.
|
||||
func (c *Client) DeleteKickstartFile(name string) error {
|
||||
_, err := c.Call("read_or_write_kickstart_template", name, false, -1, c.Token)
|
||||
return err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
Copyright 2015 Container Solutions
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cobblerclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Profile is a created profile.
|
||||
type Profile struct {
|
||||
// These are internal fields and cannot be modified.
|
||||
Ctime float64 `mapstructure:"ctime" cobbler:"noupdate"` // TODO: convert to time
|
||||
Depth int `mapstructure:"depth" cobbler:"noupdate"`
|
||||
ID string `mapstructure:"uid" cobbler:"noupdate"`
|
||||
Mtime float64 `mapstructure:"mtime" cobbler:"noupdate"` // TODO: convert to time
|
||||
ReposEnabled bool `mapstructure:"repos_enabled" cobbler:"noupdate"`
|
||||
|
||||
BootFiles string `mapstructure:"boot_files"`
|
||||
Comment string `mapstructure:"comment"`
|
||||
Distro string `mapstructure:"distro"`
|
||||
EnableGPXE bool `mapstructure:"enable_gpxe"`
|
||||
EnableMenu bool `mapstructure:"enable_menu"`
|
||||
FetchableFiles string `mapstructure:"fetchable_files"`
|
||||
KernelOptions string `mapstructure:"kernel_options"`
|
||||
KernelOptionsPost string `mapstructure:"kernel_options_post"`
|
||||
Kickstart string `mapstructure:"kickstart"`
|
||||
KSMeta string `mapstructure:"ks_meta"`
|
||||
MGMTClasses []string `mapstructure:"mgmt_classes"`
|
||||
MGMTParameters string `mapstructure:"mgmt_parameters"`
|
||||
Name string `mapstructure:"name"`
|
||||
NameServersSearch []string `mapstructure:"name_servers_search"`
|
||||
NameServers []string `mapstructure:"name_servers"`
|
||||
Owners []string `mapstructure:"owners"`
|
||||
Parent string `mapstructure:"parent"`
|
||||
Proxy string `mapstructure:"proxy"`
|
||||
RedHatManagementKey string `mapstructure:"redhat_management_key"`
|
||||
RedHatManagementServer string `mapstructure:"redhat_management_server"`
|
||||
Repos []string `mapstructure:"repos"`
|
||||
Server string `mapstructure:"server"`
|
||||
TemplateFiles string `mapstructure:"template_files"`
|
||||
TemplateRemoteKickstarts int `mapstructure:"template_remote_kickstarts"`
|
||||
VirtAutoBoot string `mapstructure:"virt_auto_boot"`
|
||||
VirtBridge string `mapstructure:"virt_bridge"`
|
||||
VirtCPUs string `mapstructure:"virt_cpus"`
|
||||
VirtDiskDriver string `mapstructure:"virt_disk_driver"`
|
||||
VirtFileSize string `mapstructure:"virt_file_size"`
|
||||
VirtPath string `mapstructure:"virt_path"`
|
||||
VirtRam string `mapstructure:"virt_ram"`
|
||||
VirtType string `mapstructure:"virt_type"`
|
||||
|
||||
Client
|
||||
}
|
||||
|
||||
// GetProfiles returns all systems in Cobbler.
|
||||
func (c *Client) GetProfiles() ([]*Profile, error) {
|
||||
var profiles []*Profile
|
||||
|
||||
result, err := c.Call("get_profiles", "-1", c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, p := range result.([]interface{}) {
|
||||
var profile Profile
|
||||
decodedResult, err := decodeCobblerItem(p, &profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedProfile := decodedResult.(*Profile)
|
||||
decodedProfile.Client = *c
|
||||
profiles = append(profiles, decodedProfile)
|
||||
}
|
||||
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
// GetProfile returns a single profile obtained by its name.
|
||||
func (c *Client) GetProfile(name string) (*Profile, error) {
|
||||
var profile Profile
|
||||
|
||||
result, err := c.Call("get_profile", name, c.Token)
|
||||
if err != nil {
|
||||
return &profile, err
|
||||
}
|
||||
|
||||
if result == "~" {
|
||||
return nil, fmt.Errorf("Profile %s not found.", name)
|
||||
}
|
||||
|
||||
decodeResult, err := decodeCobblerItem(result, &profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := decodeResult.(*Profile)
|
||||
s.Client = *c
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// CreateProfile creates a system.
|
||||
// It ensures that a Distro is set and then sets other default values.
|
||||
func (c *Client) CreateProfile(profile Profile) (*Profile, error) {
|
||||
// Check if a profile with the same name already exists
|
||||
if _, err := c.GetProfile(profile.Name); err == nil {
|
||||
return nil, fmt.Errorf("A profile with the name %s already exists.", profile.Name)
|
||||
}
|
||||
|
||||
if profile.Distro == "" {
|
||||
return nil, fmt.Errorf("A profile must have a distro set.")
|
||||
}
|
||||
|
||||
/*
|
||||
// Set default values. I guess these aren't taken care of by Cobbler?
|
||||
if system.BootFiles == "" {
|
||||
system.BootFiles = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.FetchableFiles == "" {
|
||||
system.FetchableFiles = "<<inherit>>"
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
if profile.MGMTParameters == "" {
|
||||
profile.MGMTParameters = "<<inherit>>"
|
||||
}
|
||||
|
||||
if profile.VirtAutoBoot == "" {
|
||||
profile.VirtAutoBoot = "0"
|
||||
}
|
||||
|
||||
if profile.VirtRam == "" {
|
||||
profile.VirtRam = "<<inherit>>"
|
||||
}
|
||||
|
||||
if profile.VirtType == "" {
|
||||
profile.VirtType = "<<inherit>>"
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
if system.PowerType == "" {
|
||||
system.PowerType = "ipmilan"
|
||||
}
|
||||
|
||||
if system.Status == "" {
|
||||
system.Status = "production"
|
||||
}
|
||||
|
||||
if system.VirtCPUs == "" {
|
||||
system.VirtCPUs = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtDiskDriver == "" {
|
||||
system.VirtDiskDriver = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtFileSize == "" {
|
||||
system.VirtFileSize = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtPath == "" {
|
||||
system.VirtPath = "<<inherit>>"
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// To create a profile via the Cobbler API, first call new_profile to obtain an ID
|
||||
result, err := c.Call("new_profile", c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newId := result.(string)
|
||||
|
||||
// Set the value of all fields
|
||||
item := reflect.ValueOf(&profile).Elem()
|
||||
if err := c.updateCobblerFields("profile", item, newId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save the final profile
|
||||
if _, err := c.Call("save_profile", newId, c.Token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return a clean copy of the profile
|
||||
return c.GetProfile(profile.Name)
|
||||
}
|
||||
|
||||
// UpdateProfile updates a single profile.
|
||||
func (c *Client) UpdateProfile(profile *Profile) error {
|
||||
item := reflect.ValueOf(profile).Elem()
|
||||
id, err := c.GetItemHandle("profile", profile.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.updateCobblerFields("profile", item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the final profile
|
||||
if _, err := c.Call("save_profile", id, c.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProfile deletes a single profile by its name.
|
||||
func (c *Client) DeleteProfile(name string) error {
|
||||
_, err := c.Call("remove_profile", name, c.Token)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2015 Container Solutions
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cobblerclient
|
||||
|
||||
type Snippet struct {
|
||||
Name string // The name the snippet file will be saved in Cobbler
|
||||
Body string // The contents of the kickstart file
|
||||
}
|
||||
|
||||
// Creates a snippet in Cobbler.
|
||||
// Takes a Snippet struct as input
|
||||
// Returns true/false and error if creation failed.
|
||||
func (c *Client) CreateSnippet(s Snippet) error {
|
||||
_, err := c.Call("read_or_write_snippet", s.Name, false, s.Body, c.Token)
|
||||
return err
|
||||
}
|
||||
|
||||
// Gets a snippet file in Cobbler.
|
||||
// Takes a snippet file name as input.
|
||||
// Returns *Snippet and error if read failed.
|
||||
func (c *Client) GetSnippet(name string) (*Snippet, error) {
|
||||
result, err := c.Call("read_or_write_snippet", name, true, "", c.Token)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snippet := Snippet{
|
||||
Name: name,
|
||||
Body: result.(string),
|
||||
}
|
||||
|
||||
return &snippet, nil
|
||||
}
|
||||
|
||||
// Gets a snippet file in Cobbler.
|
||||
// Takes a snippet file name as input.
|
||||
// Returns error if delete failed.
|
||||
func (c *Client) DeleteSnippet(name string) error {
|
||||
_, err := c.Call("read_or_write_snippet", name, false, -1, c.Token)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
Copyright 2015 Container Solutions
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cobblerclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// System is a created system.
|
||||
type System struct {
|
||||
// These are internal fields and cannot be modified.
|
||||
Ctime float64 `mapstructure:"ctime" cobbler:"noupdate"` // TODO: convert to time
|
||||
Depth int `mapstructure:"depth" cobbler:"noupdate"`
|
||||
ID string `mapstructure:"uid" cobbler:"noupdate"`
|
||||
IPv6Autoconfiguration bool `mapstructure:"ipv6_autoconfiguration" cobbler:"noupdate"`
|
||||
Mtime float64 `mapstructure:"mtime" cobbler:"noupdate"` // TODO: convert to time
|
||||
ReposEnabled bool `mapstructure:"repos_enabled" cobbler:"noupdate"`
|
||||
|
||||
BootFiles string `mapstructure:"boot_files"`
|
||||
Comment string `mapstructure:"comment"`
|
||||
EnableGPXE bool `mapstructure:"enable_gpxe"`
|
||||
FetchableFiles string `mapstructure:"fetchable_files"`
|
||||
Gateway string `mapstructure:"gateway"`
|
||||
Hostname string `mapstructure:"hostname"`
|
||||
Image string `mapstructure:"image"`
|
||||
Interfaces map[string]interface{} `mapstructure:"interfaces" cobbler:"noupdate"`
|
||||
IPv6DefaultDevice string `mapstructure:"ipv6_default_device"`
|
||||
KernelOptions string `mapstructure:"kernel_options"`
|
||||
KernelOptionsPost string `mapstructure:"kernel_options_post"`
|
||||
Kickstart string `mapstructure:"kickstart"`
|
||||
KSMeta string `mapstructure:"ks_meta"`
|
||||
LDAPEnabled bool `mapstructure:"ldap_enabled"`
|
||||
LDAPType string `mapstructure:"ldap_type"`
|
||||
MGMTClasses []string `mapstructure:"mgmt_classes"`
|
||||
MGMTParameters string `mapstructure:"mgmt_parameters"`
|
||||
MonitEnabled bool `mapstructure:"monit_enabled"`
|
||||
Name string `mapstructure:"name"`
|
||||
NameServersSearch []string `mapstructure:"name_servers_search"`
|
||||
NameServers []string `mapstructure:"name_servers"`
|
||||
NetbootEnabled bool `mapstructure:"netboot_enabled"`
|
||||
Owners []string `mapstructure:"owners"`
|
||||
PowerAddress string `mapstructure:"power_address"`
|
||||
PowerID string `mapstructure:"power_id"`
|
||||
PowerPass string `mapstructure:"power_pass"`
|
||||
PowerType string `mapstructure:"power_type"`
|
||||
PowerUser string `mapstructure:"power_user"`
|
||||
Profile string `mapstructure:"profile"`
|
||||
Proxy string `mapstructure:"proxy"`
|
||||
RedHatManagementKey string `mapstructure:"redhat_management_key"`
|
||||
RedHatManagementServer string `mapstructure:"redhat_management_server"`
|
||||
Status string `mapstructure:"status"`
|
||||
TemplateFiles string `mapstructure:"template_files"`
|
||||
TemplateRemoteKickstarts int `mapstructure:"template_remote_kickstarts"`
|
||||
VirtAutoBoot string `mapstructure:"virt_auto_boot"`
|
||||
VirtCPUs string `mapstructure:"virt_cpus"`
|
||||
VirtDiskDriver string `mapstructure:"virt_disk_driver"`
|
||||
VirtFileSize string `mapstructure:"virt_file_size"`
|
||||
VirtPath string `mapstructure:"virt_path"`
|
||||
VirtPXEBoot int `mapstructure:"virt_pxe_boot"`
|
||||
VirtRam string `mapstructure:"virt_ram"`
|
||||
VirtType string `mapstructure:"virt_type"`
|
||||
|
||||
Client
|
||||
}
|
||||
|
||||
// Interface is an interface in a system.
|
||||
type Interface struct {
|
||||
CNAMEs []string `mapstructure:"cnames" structs:"cnames"`
|
||||
DHCPTag string `mapstructure:"dhcp_tag" structs:"dhcp_tag"`
|
||||
DNSName string `mapstructure:"dns_name" structs:"dns_name"`
|
||||
BondingOpts string `mapstructure:"bonding_opts" structs:"bonding_opts"`
|
||||
BridgeOpts string `mapstructure:"bridge_opts" structs:"bridge_opts"`
|
||||
Gateway string `mapstructure:"if_gateway" structs:"if_gateway"`
|
||||
InterfaceType string `mapstructure:"interface_type" structs:"interface_type"`
|
||||
InterfaceMaster string `mapstructure:"interface_master" structs:"interface_master"`
|
||||
IPAddress string `mapstructure:"ip_address" structs:"ip_address"`
|
||||
IPv6Address string `mapstructure:"ipv6_address" structs:"ipv6_address"`
|
||||
IPv6Secondaries []string `mapstructure:"ipv6_secondaries" structs:"ipv6_secondaries"`
|
||||
IPv6MTU string `mapstructure:"ipv6_mtu" structs:"ipv6_mtu"`
|
||||
IPv6StaticRoutes []string `mapstructure:"ipv6_static_routes" structs:"ipv6_static_routes"`
|
||||
IPv6DefaultGateway string `mapstructure:"ipv6_default_gateway structs:"ipv6_default_gateway"`
|
||||
MACAddress string `mapstructure:"mac_address" structs:"mac_address"`
|
||||
Management bool `mapstructure:"management" structs:"managment"`
|
||||
Netmask string `mapstructure:"netmask" structs:"netmask"`
|
||||
Static bool `mapstructure:"static" structs:"static"`
|
||||
StaticRoutes []string `mapstructure:"static_routes" structs:"static_routes"`
|
||||
VirtBridge string `mapstructure:"virt_bridge" structs:"virt_bridge"`
|
||||
}
|
||||
|
||||
// Interfaces is a collection of interfaces in a system.
|
||||
type Interfaces map[string]Interface
|
||||
|
||||
// GetSystems returns all systems in Cobbler.
|
||||
func (c *Client) GetSystems() ([]*System, error) {
|
||||
var systems []*System
|
||||
|
||||
result, err := c.Call("get_systems", "", c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range result.([]interface{}) {
|
||||
var system System
|
||||
decodedResult, err := decodeCobblerItem(s, &system)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedSystem := decodedResult.(*System)
|
||||
decodedSystem.Client = *c
|
||||
systems = append(systems, decodedSystem)
|
||||
}
|
||||
|
||||
return systems, nil
|
||||
}
|
||||
|
||||
// GetSystem returns a single system obtained by its name.
|
||||
func (c *Client) GetSystem(name string) (*System, error) {
|
||||
var system System
|
||||
|
||||
result, err := c.Call("get_system", name, c.Token)
|
||||
if err != nil {
|
||||
return &system, err
|
||||
}
|
||||
|
||||
if result == "~" {
|
||||
return nil, fmt.Errorf("System %s not found.", name)
|
||||
}
|
||||
|
||||
decodeResult, err := decodeCobblerItem(result, &system)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := decodeResult.(*System)
|
||||
s.Client = *c
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// CreateSystem creates a system.
|
||||
// It ensures that either a Profile or Image are set and then sets other default values.
|
||||
func (c *Client) CreateSystem(system System) (*System, error) {
|
||||
// Check if a system with the same name already exists
|
||||
if _, err := c.GetSystem(system.Name); err == nil {
|
||||
return nil, fmt.Errorf("A system with the name %s already exists.", system.Name)
|
||||
}
|
||||
|
||||
if system.Profile == "" && system.Image == "" {
|
||||
return nil, fmt.Errorf("A system must have a profile or image set.")
|
||||
}
|
||||
|
||||
// Set default values. I guess these aren't taken care of by Cobbler?
|
||||
if system.BootFiles == "" {
|
||||
system.BootFiles = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.FetchableFiles == "" {
|
||||
system.FetchableFiles = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.MGMTParameters == "" {
|
||||
system.MGMTParameters = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.PowerType == "" {
|
||||
system.PowerType = "ipmilan"
|
||||
}
|
||||
|
||||
if system.Status == "" {
|
||||
system.Status = "production"
|
||||
}
|
||||
|
||||
if system.VirtAutoBoot == "" {
|
||||
system.VirtAutoBoot = "0"
|
||||
}
|
||||
|
||||
if system.VirtCPUs == "" {
|
||||
system.VirtCPUs = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtDiskDriver == "" {
|
||||
system.VirtDiskDriver = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtFileSize == "" {
|
||||
system.VirtFileSize = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtPath == "" {
|
||||
system.VirtPath = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtRam == "" {
|
||||
system.VirtRam = "<<inherit>>"
|
||||
}
|
||||
|
||||
if system.VirtType == "" {
|
||||
system.VirtType = "<<inherit>>"
|
||||
}
|
||||
|
||||
// To create a system via the Cobbler API, first call new_system to obtain an ID
|
||||
result, err := c.Call("new_system", c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newId := result.(string)
|
||||
|
||||
// Set the value of all fields
|
||||
item := reflect.ValueOf(&system).Elem()
|
||||
if err := c.updateCobblerFields("system", item, newId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save the final system
|
||||
if _, err := c.Call("save_system", newId, c.Token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return a clean copy of the system
|
||||
return c.GetSystem(system.Name)
|
||||
}
|
||||
|
||||
// UpdateSystem updates a single system.
|
||||
func (c *Client) UpdateSystem(system *System) error {
|
||||
item := reflect.ValueOf(system).Elem()
|
||||
id, err := c.GetItemHandle("system", system.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.updateCobblerFields("system", item, id)
|
||||
}
|
||||
|
||||
// DeleteSystem deletes a single system by its name.
|
||||
func (c *Client) DeleteSystem(name string) error {
|
||||
_, err := c.Call("remove_system", name, c.Token)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *System) CreateInterface(name string, iface Interface) error {
|
||||
i := structs.Map(iface)
|
||||
nic := make(map[string]interface{})
|
||||
for key, value := range i {
|
||||
attrName := fmt.Sprintf("%s-%s", key, name)
|
||||
nic[attrName] = value
|
||||
}
|
||||
|
||||
systemId, err := s.Client.GetItemHandle("system", s.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.Client.Call("modify_system", systemId, "modify_interface", nic, s.Client.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the final system
|
||||
if _, err := s.Client.Call("save_system", systemId, s.Client.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInterfaces returns all interfaces in a System.
|
||||
func (s *System) GetInterfaces() (Interfaces, error) {
|
||||
nics := make(Interfaces)
|
||||
for nicName, nicData := range s.Interfaces {
|
||||
var nic Interface
|
||||
if err := mapstructure.Decode(nicData, &nic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nics[nicName] = nic
|
||||
}
|
||||
|
||||
return nics, nil
|
||||
}
|
||||
|
||||
// GetInterface returns a single interface in a System.
|
||||
func (s *System) GetInterface(name string) (Interface, error) {
|
||||
nics := make(Interfaces)
|
||||
var iface Interface
|
||||
for nicName, nicData := range s.Interfaces {
|
||||
var nic Interface
|
||||
if err := mapstructure.Decode(nicData, &nic); err != nil {
|
||||
return iface, err
|
||||
}
|
||||
nics[nicName] = nic
|
||||
}
|
||||
|
||||
if iface, ok := nics[name]; ok {
|
||||
return iface, nil
|
||||
} else {
|
||||
return iface, fmt.Errorf("Interface %s not found.", name)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteInterface deletes a single interface in a System.
|
||||
func (s *System) DeleteInterface(name string) error {
|
||||
if _, err := s.GetInterface(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
systemId, err := s.Client.GetItemHandle("system", s.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.Client.Call("modify_system", systemId, "delete_interface", name, s.Client.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the final system
|
||||
if _, err := s.Client.Call("save_system", systemId, s.Client.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2012 Dmitry Maksimov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,79 @@
|
|||
## Overview
|
||||
|
||||
xmlrpc is an implementation of client side part of XMLRPC protocol in Go language.
|
||||
|
||||
## Installation
|
||||
|
||||
To install xmlrpc package run `go get github.com/kolo/xmlrpc`. To use
|
||||
it in application add `"github.com/kolo/xmlrpc"` string to `import`
|
||||
statement.
|
||||
|
||||
## Usage
|
||||
|
||||
client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi", nil)
|
||||
result := struct{
|
||||
Version string `xmlrpc:"version"`
|
||||
}{}
|
||||
client.Call("Bugzilla.version", nil, &result)
|
||||
fmt.Printf("Version: %s\n", result.Version) // Version: 4.2.7+
|
||||
|
||||
Second argument of NewClient function is an object that implements
|
||||
[http.RoundTripper](http://golang.org/pkg/net/http/#RoundTripper)
|
||||
interface, it can be used to get more control over connection options.
|
||||
By default it initialized by http.DefaultTransport object.
|
||||
|
||||
### Arguments encoding
|
||||
|
||||
xmlrpc package supports encoding of native Go data types to method
|
||||
arguments.
|
||||
|
||||
Data types encoding rules:
|
||||
* int, int8, int16, int32, int64 encoded to int;
|
||||
* float32, float64 encoded to double;
|
||||
* bool encoded to boolean;
|
||||
* string encoded to string;
|
||||
* time.Time encoded to datetime.iso8601;
|
||||
* xmlrpc.Base64 encoded to base64;
|
||||
* slice decoded to array;
|
||||
|
||||
Structs decoded to struct by following rules:
|
||||
* all public field become struct members;
|
||||
* field name become member name;
|
||||
* if field has xmlrpc tag, its value become member name.
|
||||
|
||||
Server method can accept few arguments, to handle this case there is
|
||||
special approach to handle slice of empty interfaces (`[]interface{}`).
|
||||
Each value of such slice encoded as separate argument.
|
||||
|
||||
### Result decoding
|
||||
|
||||
Result of remote function is decoded to native Go data type.
|
||||
|
||||
Data types decoding rules:
|
||||
* int, i4 decoded to int, int8, int16, int32, int64;
|
||||
* double decoded to float32, float64;
|
||||
* boolean decoded to bool;
|
||||
* string decoded to string;
|
||||
* array decoded to slice;
|
||||
* structs decoded following the rules described in previous section;
|
||||
* datetime.iso8601 decoded as time.Time data type;
|
||||
* base64 decoded to string.
|
||||
|
||||
## Implementation details
|
||||
|
||||
xmlrpc package contains clientCodec type, that implements [rpc.ClientCodec](http://golang.org/pkg/net/rpc/#ClientCodec)
|
||||
interface of [net/rpc](http://golang.org/pkg/net/rpc) package.
|
||||
|
||||
xmlrpc package works over HTTP protocol, but some internal functions
|
||||
and data type were made public to make it easier to create another
|
||||
implementation of xmlrpc that works over another protocol. To encode
|
||||
request body there is EncodeMethodCall function. To decode server
|
||||
response Response data type can be used.
|
||||
|
||||
## Contribution
|
||||
|
||||
Feel free to fork the project, submit pull requests, ask questions.
|
||||
|
||||
## Authors
|
||||
|
||||
Dmitry Maksimov (dmtmax@gmail.com)
|
|
@ -0,0 +1,144 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/rpc"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*rpc.Client
|
||||
}
|
||||
|
||||
// clientCodec is rpc.ClientCodec interface implementation.
|
||||
type clientCodec struct {
|
||||
// url presents url of xmlrpc service
|
||||
url *url.URL
|
||||
|
||||
// httpClient works with HTTP protocol
|
||||
httpClient *http.Client
|
||||
|
||||
// cookies stores cookies received on last request
|
||||
cookies http.CookieJar
|
||||
|
||||
// responses presents map of active requests. It is required to return request id, that
|
||||
// rpc.Client can mark them as done.
|
||||
responses map[uint64]*http.Response
|
||||
|
||||
response *Response
|
||||
|
||||
// ready presents channel, that is used to link request and it`s response.
|
||||
ready chan uint64
|
||||
}
|
||||
|
||||
func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
|
||||
httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
|
||||
|
||||
if codec.cookies != nil {
|
||||
for _, cookie := range codec.cookies.Cookies(codec.url) {
|
||||
httpRequest.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var httpResponse *http.Response
|
||||
httpResponse, err = codec.httpClient.Do(httpRequest)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if codec.cookies != nil {
|
||||
codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
|
||||
}
|
||||
|
||||
codec.responses[request.Seq] = httpResponse
|
||||
codec.ready <- request.Seq
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
|
||||
seq := <-codec.ready
|
||||
httpResponse := codec.responses[seq]
|
||||
|
||||
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
|
||||
return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode)
|
||||
}
|
||||
|
||||
respData, err := ioutil.ReadAll(httpResponse.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpResponse.Body.Close()
|
||||
|
||||
resp := NewResponse(respData)
|
||||
|
||||
if resp.Failed() {
|
||||
response.Error = fmt.Sprintf("%v", resp.Err())
|
||||
}
|
||||
|
||||
codec.response = resp
|
||||
|
||||
response.Seq = seq
|
||||
delete(codec.responses, seq)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = codec.response.Unmarshal(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (codec *clientCodec) Close() error {
|
||||
transport := codec.httpClient.Transport.(*http.Transport)
|
||||
transport.CloseIdleConnections()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
|
||||
func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
httpClient := &http.Client{Transport: transport}
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u, err := url.Parse(requrl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
codec := clientCodec{
|
||||
url: u,
|
||||
httpClient: httpClient,
|
||||
ready: make(chan uint64),
|
||||
responses: make(map[uint64]*http.Response),
|
||||
cookies: jar,
|
||||
}
|
||||
|
||||
return &Client{rpc.NewClientWithCodec(&codec)}, nil
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const iso8601 = "20060102T15:04:05"
|
||||
|
||||
var (
|
||||
// CharsetReader is a function to generate reader which converts a non UTF-8
|
||||
// charset into UTF-8.
|
||||
CharsetReader func(string, io.Reader) (io.Reader, error)
|
||||
|
||||
invalidXmlError = errors.New("invalid xml")
|
||||
)
|
||||
|
||||
type TypeMismatchError string
|
||||
|
||||
func (e TypeMismatchError) Error() string { return string(e) }
|
||||
|
||||
type decoder struct {
|
||||
*xml.Decoder
|
||||
}
|
||||
|
||||
func unmarshal(data []byte, v interface{}) (err error) {
|
||||
dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))}
|
||||
|
||||
if CharsetReader != nil {
|
||||
dec.CharsetReader = CharsetReader
|
||||
}
|
||||
|
||||
var tok xml.Token
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t, ok := tok.(xml.StartElement); ok {
|
||||
if t.Name.Local == "value" {
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return errors.New("non-pointer value passed to unmarshal")
|
||||
}
|
||||
if err = dec.decodeValue(val.Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read until end of document
|
||||
err = dec.Skip()
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *decoder) decodeValue(val reflect.Value) error {
|
||||
var tok xml.Token
|
||||
var err error
|
||||
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
var typeName string
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t, ok := tok.(xml.EndElement); ok {
|
||||
if t.Name.Local == "value" {
|
||||
return nil
|
||||
} else {
|
||||
return invalidXmlError
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := tok.(xml.StartElement); ok {
|
||||
typeName = t.Name.Local
|
||||
break
|
||||
}
|
||||
|
||||
// Treat value data without type identifier as string
|
||||
if t, ok := tok.(xml.CharData); ok {
|
||||
if value := strings.TrimSpace(string(t)); value != "" {
|
||||
if err = checkType(val, reflect.String); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.SetString(value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch typeName {
|
||||
case "struct":
|
||||
ismap := false
|
||||
pmap := val
|
||||
valType := val.Type()
|
||||
|
||||
if err = checkType(val, reflect.Struct); err != nil {
|
||||
if checkType(val, reflect.Map) == nil {
|
||||
if valType.Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("only maps with string key type can be unmarshalled")
|
||||
}
|
||||
ismap = true
|
||||
} else if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
var dummy map[string]interface{}
|
||||
pmap = reflect.New(reflect.TypeOf(dummy)).Elem()
|
||||
valType = pmap.Type()
|
||||
ismap = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var fields map[string]reflect.Value
|
||||
|
||||
if !ismap {
|
||||
fields = make(map[string]reflect.Value)
|
||||
|
||||
for i := 0; i < valType.NumField(); i++ {
|
||||
field := valType.Field(i)
|
||||
fieldVal := val.FieldByName(field.Name)
|
||||
|
||||
if fieldVal.CanSet() {
|
||||
if fn := field.Tag.Get("xmlrpc"); fn != "" {
|
||||
fields[fn] = fieldVal
|
||||
} else {
|
||||
fields[field.Name] = fieldVal
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create initial empty map
|
||||
pmap.Set(reflect.MakeMap(valType))
|
||||
}
|
||||
|
||||
// Process struct members.
|
||||
StructLoop:
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case xml.StartElement:
|
||||
if t.Name.Local != "member" {
|
||||
return invalidXmlError
|
||||
}
|
||||
|
||||
tagName, fieldName, err := dec.readTag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tagName != "name" {
|
||||
return invalidXmlError
|
||||
}
|
||||
|
||||
var fv reflect.Value
|
||||
ok := true
|
||||
|
||||
if !ismap {
|
||||
fv, ok = fields[string(fieldName)]
|
||||
} else {
|
||||
fv = reflect.New(valType.Elem())
|
||||
}
|
||||
|
||||
if ok {
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" {
|
||||
if err = dec.decodeValue(fv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// </value>
|
||||
if err = dec.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// </member>
|
||||
if err = dec.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ismap {
|
||||
pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv))
|
||||
val.Set(pmap)
|
||||
}
|
||||
case xml.EndElement:
|
||||
break StructLoop
|
||||
}
|
||||
}
|
||||
case "array":
|
||||
pslice := val
|
||||
if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
var dummy []interface{}
|
||||
pslice = reflect.New(reflect.TypeOf(dummy)).Elem()
|
||||
} else if err = checkType(val, reflect.Slice); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ArrayLoop:
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch t := tok.(type) {
|
||||
case xml.StartElement:
|
||||
if t.Name.Local != "data" {
|
||||
return invalidXmlError
|
||||
}
|
||||
|
||||
slice := reflect.MakeSlice(pslice.Type(), 0, 0)
|
||||
|
||||
DataLoop:
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tt := tok.(type) {
|
||||
case xml.StartElement:
|
||||
if tt.Name.Local != "value" {
|
||||
return invalidXmlError
|
||||
}
|
||||
|
||||
v := reflect.New(pslice.Type().Elem())
|
||||
if err = dec.decodeValue(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slice = reflect.Append(slice, v.Elem())
|
||||
|
||||
// </value>
|
||||
if err = dec.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
case xml.EndElement:
|
||||
pslice.Set(slice)
|
||||
val.Set(pslice)
|
||||
break DataLoop
|
||||
}
|
||||
}
|
||||
case xml.EndElement:
|
||||
break ArrayLoop
|
||||
}
|
||||
}
|
||||
default:
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data []byte
|
||||
|
||||
switch t := tok.(type) {
|
||||
case xml.EndElement:
|
||||
return nil
|
||||
case xml.CharData:
|
||||
data = []byte(t.Copy())
|
||||
default:
|
||||
return invalidXmlError
|
||||
}
|
||||
|
||||
switch typeName {
|
||||
case "int", "i4", "i8":
|
||||
if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
i, err := strconv.ParseInt(string(data), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pi := reflect.New(reflect.TypeOf(i)).Elem()
|
||||
pi.SetInt(i)
|
||||
val.Set(pi)
|
||||
} else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil {
|
||||
return err
|
||||
} else {
|
||||
i, err := strconv.ParseInt(string(data), 10, val.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.SetInt(i)
|
||||
}
|
||||
case "string", "base64":
|
||||
str := string(data)
|
||||
if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
pstr := reflect.New(reflect.TypeOf(str)).Elem()
|
||||
pstr.SetString(str)
|
||||
val.Set(pstr)
|
||||
} else if err = checkType(val, reflect.String); err != nil {
|
||||
return err
|
||||
} else {
|
||||
val.SetString(str)
|
||||
}
|
||||
case "dateTime.iso8601":
|
||||
t, err := time.Parse(iso8601, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
ptime := reflect.New(reflect.TypeOf(t)).Elem()
|
||||
ptime.Set(reflect.ValueOf(t))
|
||||
val.Set(ptime)
|
||||
} else if _, ok := val.Interface().(time.Time); !ok {
|
||||
return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind()))
|
||||
} else {
|
||||
val.Set(reflect.ValueOf(t))
|
||||
}
|
||||
case "boolean":
|
||||
v, err := strconv.ParseBool(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
pv := reflect.New(reflect.TypeOf(v)).Elem()
|
||||
pv.SetBool(v)
|
||||
val.Set(pv)
|
||||
} else if err = checkType(val, reflect.Bool); err != nil {
|
||||
return err
|
||||
} else {
|
||||
val.SetBool(v)
|
||||
}
|
||||
case "double":
|
||||
if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||||
i, err := strconv.ParseFloat(string(data), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pdouble := reflect.New(reflect.TypeOf(i)).Elem()
|
||||
pdouble.SetFloat(i)
|
||||
val.Set(pdouble)
|
||||
} else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil {
|
||||
return err
|
||||
} else {
|
||||
i, err := strconv.ParseFloat(string(data), val.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.SetFloat(i)
|
||||
}
|
||||
default:
|
||||
return errors.New("unsupported type")
|
||||
}
|
||||
|
||||
// </type>
|
||||
if err = dec.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *decoder) readTag() (string, []byte, error) {
|
||||
var tok xml.Token
|
||||
var err error
|
||||
|
||||
var name string
|
||||
for {
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if t, ok := tok.(xml.StartElement); ok {
|
||||
name = t.Name.Local
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
value, err := dec.readCharData()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return name, value, dec.Skip()
|
||||
}
|
||||
|
||||
func (dec *decoder) readCharData() ([]byte, error) {
|
||||
var tok xml.Token
|
||||
var err error
|
||||
|
||||
if tok, err = dec.Token(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t, ok := tok.(xml.CharData); ok {
|
||||
return []byte(t.Copy()), nil
|
||||
} else {
|
||||
return nil, invalidXmlError
|
||||
}
|
||||
}
|
||||
|
||||
func checkType(val reflect.Value, kinds ...reflect.Kind) error {
|
||||
if len(kinds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
match := false
|
||||
|
||||
for _, kind := range kinds {
|
||||
if val.Kind() == kind {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v",
|
||||
val.Kind(), kinds[0]))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type encodeFunc func(reflect.Value) ([]byte, error)
|
||||
|
||||
func marshal(v interface{}) ([]byte, error) {
|
||||
if v == nil {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
return encodeValue(val)
|
||||
}
|
||||
|
||||
func encodeValue(val reflect.Value) ([]byte, error) {
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
|
||||
if val.IsNil() {
|
||||
return []byte("<value/>"), nil
|
||||
}
|
||||
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
switch val.Interface().(type) {
|
||||
case time.Time:
|
||||
t := val.Interface().(time.Time)
|
||||
b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
|
||||
default:
|
||||
b, err = encodeStruct(val)
|
||||
}
|
||||
case reflect.Map:
|
||||
b, err = encodeMap(val)
|
||||
case reflect.Slice:
|
||||
b, err = encodeSlice(val)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
b = []byte(fmt.Sprintf("<double>%s</double>",
|
||||
strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
|
||||
case reflect.Bool:
|
||||
if val.Bool() {
|
||||
b = []byte("<boolean>1</boolean>")
|
||||
} else {
|
||||
b = []byte("<boolean>0</boolean>")
|
||||
}
|
||||
case reflect.String:
|
||||
var buf bytes.Buffer
|
||||
|
||||
xml.Escape(&buf, []byte(val.String()))
|
||||
|
||||
if _, ok := val.Interface().(Base64); ok {
|
||||
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
|
||||
} else {
|
||||
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
|
||||
}
|
||||
|
||||
func encodeStruct(val reflect.Value) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteString("<struct>")
|
||||
|
||||
t := val.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
b.WriteString("<member>")
|
||||
f := t.Field(i)
|
||||
|
||||
name := f.Tag.Get("xmlrpc")
|
||||
if name == "" {
|
||||
name = f.Name
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
|
||||
|
||||
p, err := encodeValue(val.FieldByName(f.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(p)
|
||||
|
||||
b.WriteString("</member>")
|
||||
}
|
||||
|
||||
b.WriteString("</struct>")
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func encodeMap(val reflect.Value) ([]byte, error) {
|
||||
var t = val.Type()
|
||||
|
||||
if t.Key().Kind() != reflect.String {
|
||||
return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteString("<struct>")
|
||||
|
||||
keys := val.MapKeys()
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
key := keys[i]
|
||||
kval := val.MapIndex(key)
|
||||
|
||||
b.WriteString("<member>")
|
||||
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
|
||||
|
||||
p, err := encodeValue(kval)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Write(p)
|
||||
b.WriteString("</member>")
|
||||
}
|
||||
|
||||
b.WriteString("</struct>")
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func encodeSlice(val reflect.Value) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteString("<array><data>")
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
p, err := encodeValue(val.Index(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Write(p)
|
||||
}
|
||||
|
||||
b.WriteString("</data></array>")
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewRequest(url string, method string, args interface{}) (*http.Request, error) {
|
||||
var t []interface{}
|
||||
var ok bool
|
||||
if t, ok = args.([]interface{}); !ok {
|
||||
if args != nil {
|
||||
t = []interface{}{args}
|
||||
}
|
||||
}
|
||||
|
||||
body, err := EncodeMethodCall(method, t...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("Content-Type", "text/xml")
|
||||
request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
b.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
|
||||
b.WriteString(fmt.Sprintf("<methodCall><methodName>%s</methodName>", method))
|
||||
|
||||
if args != nil {
|
||||
b.WriteString("<params>")
|
||||
|
||||
for _, arg := range args {
|
||||
p, err := marshal(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.WriteString(fmt.Sprintf("<param>%s</param>", string(p)))
|
||||
}
|
||||
|
||||
b.WriteString("</params>")
|
||||
}
|
||||
|
||||
b.WriteString("</methodCall>")
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`)
|
||||
)
|
||||
|
||||
type failedResponse struct {
|
||||
Code int `xmlrpc:"faultCode"`
|
||||
Error string `xmlrpc:"faultString"`
|
||||
}
|
||||
|
||||
func (r *failedResponse) err() error {
|
||||
return &xmlrpcError{
|
||||
code: r.Code,
|
||||
err: r.Error,
|
||||
}
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func NewResponse(data []byte) *Response {
|
||||
return &Response{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Failed() bool {
|
||||
return faultRx.Match(r.data)
|
||||
}
|
||||
|
||||
func (r *Response) Err() error {
|
||||
failedResp := new(failedResponse)
|
||||
if err := unmarshal(r.data, failedResp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return failedResp.err()
|
||||
}
|
||||
|
||||
func (r *Response) Unmarshal(v interface{}) error {
|
||||
if err := unmarshal(r.data, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require "xmlrpc/server"
|
||||
|
||||
class Service
|
||||
def time
|
||||
Time.now
|
||||
end
|
||||
|
||||
def upcase(s)
|
||||
s.upcase
|
||||
end
|
||||
|
||||
def sum(x, y)
|
||||
x + y
|
||||
end
|
||||
|
||||
def error
|
||||
raise XMLRPC::FaultException.new(500, "Server error")
|
||||
end
|
||||
end
|
||||
|
||||
server = XMLRPC::Server.new 5001, 'localhost'
|
||||
server.add_handler "service", Service.new
|
||||
server.serve
|
|
@ -0,0 +1,19 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// xmlrpcError represents errors returned on xmlrpc request.
|
||||
type xmlrpcError struct {
|
||||
code int
|
||||
err string
|
||||
}
|
||||
|
||||
// Error() method implements Error interface
|
||||
func (e *xmlrpcError) Error() string {
|
||||
return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code)
|
||||
}
|
||||
|
||||
// Base64 represents value in base64 encoding
|
||||
type Base64 string
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
layout: "cobbler"
|
||||
page_title: "Provider: Cobbler"
|
||||
sidebar_current: "docs-cobbler-index"
|
||||
description: |-
|
||||
The Cobbler provider is used to interact with a locally installed,
|
||||
Cobbler service.
|
||||
---
|
||||
|
||||
# Cobbler Provider
|
||||
|
||||
The Cobbler provider is used to interact with a locally installed
|
||||
[Cobbler](http://cobbler.github.io) service. The provider needs
|
||||
to be configured with the proper credentials before it can be used.
|
||||
|
||||
Use the navigation to the left to read about the available resources.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Configure the Cobbler provider
|
||||
provider "cobbler" {
|
||||
username = "${var.cobbler_username}"
|
||||
password = "${var.cobbler_password}"
|
||||
url = "${var.cobbler_url}"
|
||||
}
|
||||
|
||||
# Create a Cobbler Distro
|
||||
resource "cobbler_distro" "ubuntu-1404-x86_64" {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `username` - (Required) The username to the Cobbler service. This can
|
||||
also be specified with the `COBBLER_USERNAME` shell environment variable.
|
||||
|
||||
* `password` - (Required) The password to the Cobbler service. This can
|
||||
also be specified with the `COBBLER_PASSWORD` shell environment variable.
|
||||
|
||||
* `url` - (Required) The url to the Cobbler service. This can
|
||||
also be specified with the `COBBLER_URL` shell environment variable.
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
layout: "cobbler"
|
||||
page_title: "Cobbler: cobbler_distro"
|
||||
sidebar_current: "docs-cobbler-resource-distro"
|
||||
description: |-
|
||||
Manages a distribution within Cobbler.
|
||||
---
|
||||
|
||||
# cobbler\_distro
|
||||
|
||||
Manages a distribution within Cobbler.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "cobbler_distro" "ubuntu-1404-x86_64" {
|
||||
name = "foo"
|
||||
breed = "ubuntu"
|
||||
os_version = "trusty"
|
||||
arch = "x86_64"
|
||||
kernel = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/linux"
|
||||
initrd = "/var/www/cobbler/ks_mirror/Ubuntu-14.04/install/netboot/ubuntu-installer/amd64/initrd.gz"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `arch` - (Required) The architecture of the distro. Valid options
|
||||
are: i386, x86_64, ia64, ppc, ppc64, s390, arm.
|
||||
|
||||
* `breed` - (Required) The "breed" of distribution. Valid options
|
||||
are: redhat, fedora, centos, scientific linux, suse, debian, and
|
||||
ubuntu. These choices may vary depending on the version of Cobbler
|
||||
in use.
|
||||
|
||||
* `boot_files` - (Optional) Files copied into tftpboot beyond the
|
||||
kernel/initrd.
|
||||
|
||||
* `comment` - (Optional) Free form text description.
|
||||
|
||||
* `fetchable_files` - (Optional) Templates for tftp or wget.
|
||||
|
||||
* `kernel` - (Required) Absolute path to kernel on filesystem. This
|
||||
must already exist prior to creating the distro.
|
||||
|
||||
* `kernel_options` - (Optional) Kernel options to use with the
|
||||
kernel.
|
||||
|
||||
* `kernel_options_post` - (Optional) Post install Kernel options to
|
||||
use with the kernel after installation.
|
||||
|
||||
* `initrd` - (Required) Absolute path to initrd on filesystem. This
|
||||
must already exist prior to creating the distro.
|
||||
|
||||
* `mgmt_classes` - (Optional) Management classes for external config
|
||||
management.
|
||||
|
||||
* `name` - (Required) A name for the distro.
|
||||
|
||||
* `os_version` - (Required) The version of the distro you are
|
||||
creating. This varies with the version of Cobbler you are using.
|
||||
An updated signature list may need to be obtained in order to
|
||||
support a newer version. Example: `trusty`.
|
||||
|
||||
* `owners` - (Optional) Owners list for authz_ownership.
|
||||
|
||||
* `redhat_management_key` - (Optional) Red Hat Management key.
|
||||
|
||||
* `redhat_management_server` - (Optional) Red Hat Management server.
|
||||
|
||||
* `template_files` - (Optional) File mappings for built-in config
|
||||
management.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
All of the above Optional attributes are also exported.
|
||||
|
||||
## Notes
|
||||
|
||||
The path to the `kernel` and `initrd` files must exist before
|
||||
creating a Distro. Usually this involves running `cobbler import ...`
|
||||
prior to creating the Distro.
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
layout: "cobbler"
|
||||
page_title: "Cobbler: cobbler_kickstart_file"
|
||||
sidebar_current: "docs-cobbler-resource-kickstart_file"
|
||||
description: |-
|
||||
Manages a Kickstart File within Cobbler.
|
||||
---
|
||||
|
||||
# cobbler\_kickstart\_file
|
||||
|
||||
Manages a Kickstart File within Cobbler.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "cobbler_kickstart_file" "my_kickstart" {
|
||||
name = "/var/lib/cobbler/kickstarts/my_kickstart.ks"
|
||||
body = "<content of kickstart file>"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `body` - (Required) The body of the kickstart file.
|
||||
|
||||
* `name` - (Required) The name of the kickstart file. This must be
|
||||
the full path, including `/var/lib/cobbler/kickstarts`.
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
layout: "cobbler"
|
||||
page_title: "Cobbler: cobbler_profile"
|
||||
sidebar_current: "docs-cobbler-resource-profile"
|
||||
description: |-
|
||||
Manages a Profile within Cobbler.
|
||||
---
|
||||
|
||||
# cobbler\_profile
|
||||
|
||||
Manages a Profile within Cobbler.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "cobbler_profile" "my_profile" {
|
||||
name = "/var/lib/cobbler/snippets/my_snippet"
|
||||
distro = "ubuntu-1404-x86_64"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `boot_files` - (Optional) Files copied into tftpboot beyond the
|
||||
kernel/initrd.
|
||||
|
||||
* `comment` - (Optional) Free form text description.
|
||||
|
||||
* `distro` - (Optional) Parent distribution.
|
||||
|
||||
* `enable_gpxe` - (Optional) Use gPXE instead of PXELINUX for
|
||||
advanced booting options.
|
||||
|
||||
* `enable_menu` - (Optional) Enable a boot menu.
|
||||
|
||||
* `fetchable_files` - (Optional) Templates for tftp or wget.
|
||||
|
||||
* `kernel_options` - (Optional) Kernel options for the profile.
|
||||
|
||||
* `kernel_options_post` - (Optional) Post install kernel options.
|
||||
|
||||
* `kickstart` - (Optional) The kickstart file to use.
|
||||
|
||||
* `ks_meta` - (Optional) Kickstart metadata.
|
||||
|
||||
* `mgmt_classes` - (Optional) For external configuration management.
|
||||
|
||||
* `mgmt_parameters` - (Optional) Parameters which will be handed to
|
||||
your management application (Must be a valid YAML dictionary).
|
||||
|
||||
* `name_servers_search` - (Optional) Name server search settings.
|
||||
|
||||
* `name_servers` - (Optional) Name servers.
|
||||
|
||||
* `name` - (Required) The name of the profile.
|
||||
|
||||
* `owners` - (Optional) Owners list for authz_ownership.
|
||||
|
||||
* `proxy` - (Optional) Proxy URL.
|
||||
|
||||
* `redhat_management_key` - (Optional) Red Hat Management Key.
|
||||
|
||||
* `redhat_management_server` - (Optional) RedHat Management Server.
|
||||
|
||||
* `repos` - (Optional) Repos to auto-assign to this profile.
|
||||
|
||||
* `template_files` - (Optional) File mappings for built-in config
|
||||
management.
|
||||
|
||||
* `template_remote_kickstarts` - (Optional) remote kickstart
|
||||
templates.
|
||||
|
||||
* `virt_auto_boot` - (Optional) Auto boot virtual machines.
|
||||
|
||||
* `virt_bridge` - (Optional) The bridge for virtual machines.
|
||||
|
||||
* `virt_cpus` - (Optional) The number of virtual CPUs.
|
||||
|
||||
* `virt_file_size` - (Optional) The virtual machine file size.
|
||||
|
||||
* `virt_path` - (Optional) The virtual machine path.
|
||||
|
||||
* `virt_ram` - (Optional) The amount of RAM for the virtual machine.
|
||||
|
||||
* `virt_type` - (Optional) The type of virtual machine. Valid options
|
||||
are: xenpv, xenfv, qemu, kvm, vmware, openvz.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
All of the above Optional attributes are also exported.
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
layout: "cobbler"
|
||||
page_title: "Cobbler: cobbler_snippet"
|
||||
sidebar_current: "docs-cobbler-resource-snippet"
|
||||
description: |-
|
||||
Manages a Snippet within Cobbler.
|
||||
---
|
||||
|
||||
# cobbler\_snippet
|
||||
|
||||
Manages a Snippet within Cobbler.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "cobbler_snippet" "my_snippet" {
|
||||
name = "/var/lib/cobbler/snippets/my_snippet"
|
||||
body = "<content of snippet>"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `body` - (Required) The body of the snippet.
|
||||
|
||||
* `name` - (Required) The name of the snippet. This must be the full
|
||||
path, including `/var/lib/cobbler/snippets`.
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
layout: "cobbler"
|
||||
page_title: "Cobbler: cobbler_system"
|
||||
sidebar_current: "docs-cobbler-resource-system"
|
||||
description: |-
|
||||
Manages a System within Cobbler.
|
||||
---
|
||||
|
||||
# cobbler\_system
|
||||
|
||||
Manages a System within Cobbler.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "cobbler_system" "my_system" {
|
||||
name = "my_system"
|
||||
profile = "${cobbler_profile.my_profile.name}"
|
||||
name_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
comment = "I'm a system"
|
||||
|
||||
interface {
|
||||
name = "eth0"
|
||||
mac_address = "aa:bb:cc:dd:ee:ff"
|
||||
static = true
|
||||
ip_address = "1.2.3.4"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
|
||||
interface {
|
||||
name = "eth1"
|
||||
mac_address = "aa:bb:cc:dd:ee:fa"
|
||||
static = true
|
||||
ip_address = "1.2.3.5"
|
||||
netmask = "255.255.255.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `boot_files` - (Optional) TFTP boot files copied into tftpboot.
|
||||
|
||||
* `comment` - (Optional) Free form text description
|
||||
|
||||
* `enable_gpxe` - (Optional) Use gPXE instead of PXELINUX.
|
||||
|
||||
* `fetchable_files` - (Optional) Templates for tftp or wget.
|
||||
|
||||
* `gateway` - (Optional) Network gateway.
|
||||
|
||||
* `hostname` - (Optional) Hostname of the system.
|
||||
|
||||
* `image` - (Optional) Parent image (if no profile is used).
|
||||
|
||||
* `interface` - (Optional)
|
||||
|
||||
* `ipv6_default_device` - (Optional) IPv6 default device.
|
||||
|
||||
* `kernel_options` - (Optional) Kernel options.
|
||||
ex: selinux=permissive.
|
||||
|
||||
* `kernel_options_post` - (Optional) Kernel options (post install).
|
||||
|
||||
* `kickstart` - (Optional) Path to kickstart template.
|
||||
|
||||
* `ks_meta` - (Optional) Kickstart metadata.
|
||||
|
||||
* `ldap_enabled` - (Optional) Configure LDAP at next config update.
|
||||
|
||||
* `ldap_type` - (Optional) LDAP management type.
|
||||
|
||||
* `mgmt_classes` - (Optional) Management classes for external config
|
||||
management.
|
||||
* `mgmt_parameters` - (Optional) Parameters which will be handed to
|
||||
your management application. Must be a valid YAML dictionary.
|
||||
|
||||
* `monit_enabled` - (Optional) Configure monit on this machine at
|
||||
next config update.
|
||||
|
||||
* `name_servers_search` - (Optional) Name servers search path.
|
||||
|
||||
* `name_servers` - (Optional) Name servers.
|
||||
|
||||
* `name` - (Required) The name of the system.
|
||||
|
||||
* `netboot_enabled` - (Optional) (re)Install this machine at next
|
||||
boot.
|
||||
|
||||
* `owners` - (Optional) Owners list for authz_ownership.
|
||||
|
||||
* `power_address` - (Optional) Power management address.
|
||||
|
||||
* `power_id` - (Optional) Usually a plug number or blade name if
|
||||
power type requires it.
|
||||
|
||||
* `power_pass` - (Optional) Power management password.
|
||||
|
||||
* `power_type` - (Optional) Power management type.
|
||||
|
||||
* `power_user` - (Optional) Power management user.
|
||||
|
||||
* `profile` - (Required) Parent profile.
|
||||
|
||||
* `proxy` - (Optional) Proxy URL.
|
||||
|
||||
* `redhat_management_key` - (Optional) Red Hat management key.
|
||||
|
||||
* `redhat_management_server` - (Optional) Red Hat management server.
|
||||
|
||||
* `status` - (Optional) System status (development, testing,
|
||||
acceptance, production).
|
||||
|
||||
* `template_files` - (Optional) File mappings for built-in
|
||||
configuration management.
|
||||
|
||||
* `template_remote_kickstarts` - (Optional) template remote
|
||||
kickstarts.
|
||||
|
||||
* `virt_auto_boot` - (Optional) Auto boot the VM.
|
||||
|
||||
* `virt_cpus` - (Optional) Number of virtual CPUs in the VM.
|
||||
|
||||
* `virt_disk_driver` - (Optional) The on-disk format for the
|
||||
virtualization disk.
|
||||
|
||||
* `virt_file_size` - (Optional) Virt file size.
|
||||
|
||||
* `virt_path` - (Optional) Path to the VM.
|
||||
|
||||
* `virt_pxe_boot` - (Optional) Use PXE to build this VM?
|
||||
|
||||
* `virt_ram` - (Optional) The amount of RAM for the VM.
|
||||
|
||||
* `virt_type` - (Optional) Virtualization technology to use: xenpv,
|
||||
xenfv, qemu, kvm, vmware, openvz.
|
||||
|
||||
The `interface` block supports:
|
||||
|
||||
* `name` - (Required) The device name of the interface. ex: eth0.
|
||||
|
||||
* `cnames` - (Optional) Canonical name records.
|
||||
|
||||
* `dhcp_tag` - (Optional) DHCP tag.
|
||||
|
||||
* `dns_name` - (Optional) DNS name.
|
||||
|
||||
* `bonding_opts` - (Optional) Options for bonded interfaces.
|
||||
|
||||
* `bridge_opts` - (Optional) Options for bridge interfaces.
|
||||
|
||||
* `gateway` - (Optional) Per-interface gateway.
|
||||
|
||||
* `interface_type` - (Optional) The type of interface: na, master,
|
||||
slave, bond, bond_slave, bridge, bridge_slave, bonded_bridge_slave.
|
||||
|
||||
* `interface_master` - (Optional) The master interface when slave.
|
||||
|
||||
* `ip_address` - (Optional) The IP address of the interface.
|
||||
|
||||
* `ipv6_address` - (Optional) The IPv6 address of the interface.
|
||||
|
||||
* `ipv6_mtu` - (Optional) The MTU of the IPv6 adress.
|
||||
|
||||
* `ipv6_static_routes` - (Optional) Static routes for the IPv6
|
||||
interface.
|
||||
|
||||
* `ipv6_default_gateway` - (Optional) The default gateawy for the
|
||||
IPv6 address / interface.
|
||||
|
||||
* `mac_address` - (Optional) The MAC address of the interface.
|
||||
|
||||
* `management` - (Optional) Whether this interface is a management
|
||||
interface.
|
||||
|
||||
* `netmask` - (Optional) The IPv4 netmask of the interface.
|
||||
|
||||
* `static` - (Optional) Whether the interface should be static or
|
||||
DHCP.
|
||||
|
||||
* `static_routes` - (Optional) Static routes for the interface.
|
||||
|
||||
* `virt_bridge` - (Optional) The virtual bridge to attach to.
|
||||
|
||||
## Attribute Reference
|
||||
|
||||
All optional attributes listed above are also exported.
|
|
@ -0,0 +1,38 @@
|
|||
<% wrap_layout :inner do %>
|
||||
<% content_for :sidebar do %>
|
||||
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/providers/index.html">« Documentation Home</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-cobbler-index") %>>
|
||||
<a href="/docs/providers/cobbler/index.html">Cobbler Provider</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current(/^docs-cobbler-resource/) %>>
|
||||
<a href="#">Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-cobbler-resource-distro") %>>
|
||||
<a href="/docs/providers/cobbler/r/distro.html">cobbler_distro</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-cobbler-resource-kickstart_file") %>>
|
||||
<a href="/docs/providers/cobbler/r/kickstart_file.html">cobbler_kickstart_file</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-cobbler-resource-profile") %>>
|
||||
<a href="/docs/providers/cobbler/r/profile.html">cobbler_profile</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-cobbler-resource-snippet") %>>
|
||||
<a href="/docs/providers/cobbler/r/snippet.html">cobbler_snippet</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-cobbler-resource-system") %>>
|
||||
<a href="/docs/providers/cobbler/r/system.html">cobbler_system</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
|
@ -165,6 +165,10 @@
|
|||
<a href="/docs/providers/cloudstack/index.html">CloudStack</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-cobbler") %>>
|
||||
<a href="/docs/providers/cobbler/index.html">Cobbler</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-consul") %>>
|
||||
<a href="/docs/providers/consul/index.html">Consul</a>
|
||||
</li>
|
||||
|
@ -256,7 +260,7 @@
|
|||
<li<%= sidebar_current("docs-providers-tls") %>>
|
||||
<a href="/docs/providers/tls/index.html">TLS</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li<%= sidebar_current("docs-providers-triton") %>>
|
||||
<a href="/docs/providers/triton/index.html">Triton</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue