### Install go.yaml.in/yaml/v3 Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/go.yaml.in/yaml/v3/README.md Use 'go get' to install the latest version of the YAML package for Go. ```bash go get go.yaml.in/yaml/v3 ``` -------------------------------- ### Migrate Format Strings to Structured Logs Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/go-logr/logr/README.md Examples demonstrating the conversion of klog format-string logging to structured logr-style logging. ```go klog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err) ``` ```go logger.Error(err, "client returned an error", "code", responseCode) ``` ```go klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url) ``` ```go logger.V(4).Info("got a retry-after response when requesting url", "attempt", retries, "after seconds", seconds, "url", url) ``` -------------------------------- ### Setup BOSH CPI Executable with RPC Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt This Go code demonstrates how to set up the RPC handler for processing BOSH Director requests. It includes creating a logger, a CPI factory, and initializing the CLI to serve requests. ```go package main import ( "os" boshlog "github.com/cloudfoundry/bosh-utils/logger" "github.com/cloudfoundry/bosh-cpi-go/apiv1" "github.com/cloudfoundry/bosh-cpi-go/rpc" ) func main() { // Create logger logger := boshlog.NewLogger(boshlog.LevelDebug) // Create CPI factory with configuration cpiFactory := &MyCPIFactory{ config: loadConfig(), } // Create CLI from factory cli := rpc.NewFactory(logger).NewCLI(cpiFactory) // Process single request (typical for BOSH CPI) err := cli.ServeOnce() if err != nil { logger.Error("main", "Failed to serve: %s", err) os.Exit(1) } } // For custom input/output (testing, debugging) func runWithCustomIO() { logger := boshlog.NewLogger(boshlog.LevelNone) factory := rpc.NewFactory(logger) // Use custom readers/writers cli := factory.NewCLIWithInOut(os.Stdin, os.Stdout, &MyCPIFactory{}) cli.ServeOnce() } ``` -------------------------------- ### Create Root Logger with logimpl Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/go-logr/logr/README.md Example of creating the root logger using a specific implementation like 'logimpl'. This is typically done early in an application's lifecycle. ```go func main() { // ... other setup code ... // Create the "root" logger. We have chosen the "logimpl" implementation, // which takes some initial parameters and returns a logr.Logger. logger := logimpl.New(param1, param2) // ... other setup code ... } ``` -------------------------------- ### Configure Networking in CPI Methods Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Illustrates how to work with network configurations within CPI methods, including retrieving IP, gateway, and CIDR details, setting MAC addresses, adding custom routes, and marking networks as preconfigured. Also shows how to backfill DNS and get the default gateway network. ```go // Working with networks in CPI methods func (c *MyCPI) configureNetworking(networks apiv1.Networks) { for name, net := range networks { if net.IsDynamic() { // DHCP network - cloud assigns IP continue } // Static network configuration ip := net.IP() gateway := net.Gateway() cidr := net.SubnetCIDR() // e.g., "10.0.0.0/24" ipCIDR := net.IPWithSubnetMask() // e.g., "10.0.0.5/24" // Set MAC address after VM creation net.SetMAC("00:11:22:33:44:55") // Add custom routes net.AddRoute("192.168.1.0", "255.255.255.0", "10.0.0.1") // Mark as preconfigured (no agent network setup needed) net.SetPreconfigured() // Check network cloud properties var netProps struct { SubnetID string `json:"subnet_id"` } net.CloudProps().As(&netProps) _ = name _ = ip _ = gateway _ = cidr _ = ipCIDR } // Backfill DNS for default network if empty networks.BackfillDefaultDNS([]string{"8.8.8.8"}) // Get default gateway network defaultNet := networks.Default() _ = defaultNet.Gateway() } ``` -------------------------------- ### Minimal BOSH CPI Go Implementation Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt This is a complete, minimal implementation of the BOSH CPI interface in Go. It includes all required methods for interacting with a cloud provider. Use this as a starting point for building custom CPIs. ```go package main import ( "os" boshlog "github.com/cloudfoundry/bosh-utils/logger" "github.com/cloudfoundry/bosh-cpi-go/apiv1" "github.com/cloudfoundry/bosh-cpi-go/rpc" ) type CPIFactory struct{} type CPI struct{} var _ apiv1.CPIFactory = CPIFactory{} var _ apiv1.CPI = CPI{} func main() { logger := boshlog.NewLogger(boshlog.LevelNone) cli := rpc.NewFactory(logger).NewCLI(CPIFactory{}) if err := cli.ServeOnce(); err != nil { logger.Error("main", "Serving: %s", err) os.Exit(1) } } func (f CPIFactory) New(_ apiv1.CallContext) (apiv1.CPI, error) { return CPI{}, nil } func (c CPI) Info() (apiv1.Info, error) { return apiv1.Info{StemcellFormats: []string{"raw"}}, nil } // Stemcells func (c CPI) CreateStemcell(imagePath string, _ apiv1.StemcellCloudProps) (apiv1.StemcellCID, error) { return apiv1.NewStemcellCID("sc-" + generateID()), nil } func (c CPI) DeleteStemcell(cid apiv1.StemcellCID) error { return nil } // VMs func (c CPI) CreateVM(agentID apiv1.AgentID, stemcellCID apiv1.StemcellCID, cloudProps apiv1.VMCloudProps, networks apiv1.Networks, diskCIDs []apiv1.DiskCID, env apiv1.VMEnv) (apiv1.VMCID, error) { return apiv1.NewVMCID("vm-" + generateID()), nil } func (c CPI) CreateVMV2(agentID apiv1.AgentID, stemcellCID apiv1.StemcellCID, cloudProps apiv1.VMCloudProps, networks apiv1.Networks, diskCIDs []apiv1.DiskCID, env apiv1.VMEnv) (apiv1.VMCID, apiv1.Networks, error) { vmCID, err := c.CreateVM(agentID, stemcellCID, cloudProps, networks, diskCIDs, env) return vmCID, networks, err } func (c CPI) DeleteVM(cid apiv1.VMCID) error { return nil } func (c CPI) HasVM(cid apiv1.VMCID) (bool, error) { return true, nil } func (c CPI) RebootVM(cid apiv1.VMCID) error { return nil } func (c CPI) SetVMMetadata(cid apiv1.VMCID, meta apiv1.VMMeta) error { return nil } func (c CPI) GetDisks(cid apiv1.VMCID) ([]apiv1.DiskCID, error) { return nil, nil } func (c CPI) CalculateVMCloudProperties(r apiv1.VMResources) (apiv1.VMCloudProps, error) { return apiv1.NewVMCloudPropsFromMap(map[string]interface{}{}), nil } // Disks func (c CPI) CreateDisk(size int, props apiv1.DiskCloudProps, vm *apiv1.VMCID) (apiv1.DiskCID, error) { return apiv1.NewDiskCID("disk-" + generateID()), nil } func (c CPI) DeleteDisk(cid apiv1.DiskCID) error { return nil } func (c CPI) HasDisk(cid apiv1.DiskCID) (bool, error) { return true, nil } func (c CPI) AttachDisk(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) error { return nil } func (c CPI) AttachDiskV2(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) (apiv1.DiskHint, error) { return apiv1.NewDiskHintFromString("/dev/sdb"), nil } func (c CPI) DetachDisk(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) error { return nil } func (c CPI) ResizeDisk(cid apiv1.DiskCID, size int) error { return nil } func (c CPI) SetDiskMetadata(cid apiv1.DiskCID, meta apiv1.DiskMeta) error { return nil } // Snapshots func (c CPI) SnapshotDisk(cid apiv1.DiskCID, meta apiv1.DiskMeta) (apiv1.SnapshotCID, error) { return apiv1.NewSnapshotCID("snap-" + generateID()), nil } func (c CPI) DeleteSnapshot(cid apiv1.SnapshotCID) error { return nil } func generateID() string { return "123456" } // Replace with actual ID generation ``` -------------------------------- ### Template Function Usage Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/go-task/slim-sprig/v3/README.md Example of chaining template functions within a Go template string. ```text {{ "hello!" | upper | repeat 5 }} ``` -------------------------------- ### Create Agent Environment Configuration Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Demonstrates creating an agent environment configuration for a BOSH VM using AgentEnvFactory. This includes setting up agent communication options, attaching system, ephemeral, and persistent disks with specific hints, and serializing the environment to bytes. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) func (c *MyCPI) createAgentEnv( agentID apiv1.AgentID, vmCID apiv1.VMCID, networks apiv1.Networks, env apiv1.VMEnv, ) ([]byte, error) { // Configure agent communication options agentOpts := apiv1.AgentOptions{ Mbus: "https://mbus:password@0.0.0.0:6868/agent", NTP: []string{"0.pool.ntp.org", "1.pool.ntp.org"}, } // Validate options if err := agentOpts.Validate(); err != nil { return nil, err } // Create agent environment factory := apiv1.NewAgentEnvFactory() agentEnv := factory.ForVM(agentID, vmCID, networks, env, agentOpts) // Configure disk hints agentEnv.AttachSystemDisk(apiv1.NewDiskHintFromString("/dev/sda")) agentEnv.AttachEphemeralDisk(apiv1.NewDiskHintFromString("/dev/sdb")) // Attach persistent disks diskCID := apiv1.NewDiskCID("disk-123") agentEnv.AttachPersistentDisk(diskCID, apiv1.NewDiskHintFromMap(map[string]interface{}{ "path": "/dev/sdc", "volume_id": "vol-123", })) // Serialize to JSON for storing in VM metadata return agentEnv.AsBytes() } ``` -------------------------------- ### Build All Files (Old System) Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/golang.org/x/sys/unix/README.md Use this script to generate Go files for your current OS and architecture with the old build system. Ensure GOOS and GOARCH are set correctly. ```bash mkall.sh ``` -------------------------------- ### Implement CreateVM and CreateVMV2 Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Handles VM provisioning, disk attachment, and network configuration. CreateVMV2 is an extended version that returns resolved network details like MAC addresses. ```go package main import ( "fmt" "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) // Cloud-specific VM properties type VMProps struct { InstanceType string `json:"instance_type"` Zone string `json:"zone"` Tags map[string]string `json:"tags"` } func (c *MyCPI) CreateVM( agentID apiv1.AgentID, stemcellCID apiv1.StemcellCID, cloudProps apiv1.VMCloudProps, networks apiv1.Networks, diskCIDs []apiv1.DiskCID, env apiv1.VMEnv, ) (apiv1.VMCID, error) { // Parse cloud-specific properties var props VMProps if err := cloudProps.As(&props); err != nil { return apiv1.VMCID{}, fmt.Errorf("parsing cloud props: %w", err) } // Get default network for IP assignment defaultNet := networks.Default() // Create VM in your cloud vmID, err := c.client.CreateInstance( stemcellCID.AsString(), props.InstanceType, props.Zone, defaultNet.IP(), ) if err != nil { return apiv1.VMCID{}, err } // Attach any pre-existing disks for _, diskCID := range diskCIDs { if err := c.client.AttachDisk(vmID, diskCID.AsString()); err != nil { c.client.DeleteInstance(vmID) // Cleanup on failure return apiv1.VMCID{}, err } } return apiv1.NewVMCID(vmID), nil } // CreateVMV2 returns networks with resolved values (MAC addresses, etc.) func (c *MyCPI) CreateVMV2( agentID apiv1.AgentID, stemcellCID apiv1.StemcellCID, cloudProps apiv1.VMCloudProps, networks apiv1.Networks, diskCIDs []apiv1.DiskCID, env apiv1.VMEnv, ) (apiv1.VMCID, apiv1.Networks, error) { vmCID, err := c.CreateVM(agentID, stemcellCID, cloudProps, networks, diskCIDs, env) if err != nil { return apiv1.VMCID{}, nil, err } // Update networks with cloud-assigned MAC addresses for name, net := range networks { mac, _ := c.client.GetNetworkInterface(vmCID.AsString(), name) net.SetMAC(mac) } return vmCID, networks, nil } ``` -------------------------------- ### Show Build Commands (Old System) Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/golang.org/x/sys/unix/README.md Run this command with the -n flag to see the build commands that will be executed by mkall.sh without actually running them. ```bash mkall.sh -n ``` -------------------------------- ### Create Network Configurations Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Defines how to programmatically create network configurations for BOSH VMs, supporting both manual (static IP) and dynamic (DHCP) network types. Ensure correct network options are provided for each type. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) // Creating networks programmatically func createNetworkConfig() apiv1.Networks { networks := apiv1.Networks{} // Create a manual (static) network networks["default"] = apiv1.NewNetwork(apiv1.NetworkOpts{ Type: "manual", IP: "10.0.0.5", Netmask: "255.255.255.0", Gateway: "10.0.0.1", DNS: []string{"8.8.8.8", "8.8.4.4"}, Default: []string{"dns", "gateway"}, }) // Create a dynamic (DHCP) network networks["vip"] = apiv1.NewNetwork(apiv1.NetworkOpts{ Type: "dynamic", }) return networks } ``` -------------------------------- ### Implement CalculateVMCloudProperties Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Maps abstract resource requirements like RAM and CPU to specific cloud instance types. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) func (c *MyCPI) CalculateVMCloudProperties(res apiv1.VMResources) (apiv1.VMCloudProps, error) { // res.RAM is in MB, res.CPU is core count, res.EphemeralDiskSize is in MB instanceType := c.findBestInstanceType(res.RAM, res.CPU, res.EphemeralDiskSize) return apiv1.NewVMCloudPropsFromMap(map[string]interface{}{ "instance_type": instanceType, "ephemeral_disk_size": res.EphemeralDiskSize, }), nil } func (c *MyCPI) findBestInstanceType(ramMB, cpus, diskMB int) string { // Map requirements to cloud instance types if ramMB <= 1024 && cpus <= 1 { return "small" } else if ramMB <= 4096 && cpus <= 2 { return "medium" } return "large" } ``` -------------------------------- ### CreateVM and CreateVMV2 Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Handles the creation of new virtual machines. CreateVMV2 additionally returns resolved network configurations. ```APIDOC ## POST /api/v1/vms ### Description Creates a new virtual machine with the specified stemcell, cloud properties, and networks. CreateVMV2 (API v2) additionally returns the resolved network configuration after cloud processing. ### Method POST ### Endpoint /api/v1/vms ### Parameters #### Request Body - **agent_id** (apiv1.AgentID) - Required - The agent identifier. - **stemcell_cid** (apiv1.StemcellCID) - Required - The stemcell identifier. - **cloud_props** (apiv1.VMCloudProps) - Required - Cloud-specific VM properties. - **networks** (apiv1.Networks) - Required - Network configuration. - **disk_cids** (array of apiv1.DiskCID) - Optional - Pre-existing disk identifiers to attach. - **env** (apiv1.VMEnv) - Optional - Environment variables for the VM. ### Response #### Success Response (200) - **vm_cid** (apiv1.VMCID) - The identifier of the created virtual machine. - **networks** (apiv1.Networks) - (Only for CreateVMV2) The resolved network configuration. #### Response Example (CreateVM) { "vm_cid": "vm-12345" } #### Response Example (CreateVMV2) { "vm_cid": "vm-12345", "networks": { "default": { "type": "dynamic", "ip": "192.168.1.10", "netmask": "255.255.255.0", "gateway": "192.168.1.1", "mac": "00:0a:95:9d:68:16" } } } ``` -------------------------------- ### Implement VM Lifecycle and Metadata Operations Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Provides methods for deleting, checking existence, rebooting, tagging, and listing disks for a VM. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) func (c *MyCPI) DeleteVM(cid apiv1.VMCID) error { // Detach all disks before deletion disks, _ := c.client.ListAttachedDisks(cid.AsString()) for _, diskID := range disks { c.client.DetachDisk(cid.AsString(), diskID) } return c.client.DeleteInstance(cid.AsString()) } func (c *MyCPI) HasVM(cid apiv1.VMCID) (bool, error) { _, err := c.client.GetInstance(cid.AsString()) if err != nil { if isNotFoundError(err) { return false, nil } return false, err } return true, nil } func (c *MyCPI) RebootVM(cid apiv1.VMCID) error { return c.client.RebootInstance(cid.AsString()) } func (c *MyCPI) SetVMMetadata(cid apiv1.VMCID, metadata apiv1.VMMeta) error { // Extract metadata as map var meta map[string]interface{} // VMMeta wraps cloud key-values return c.client.SetInstanceTags(cid.AsString(), meta) } func (c *MyCPI) GetDisks(cid apiv1.VMCID) ([]apiv1.DiskCID, error) { diskIDs, err := c.client.ListAttachedDisks(cid.AsString()) if err != nil { return nil, err } var cids []apiv1.DiskCID for _, id := range diskIDs { cids = append(cids, apiv1.NewDiskCID(id)) } return cids, nil } ``` -------------------------------- ### Validate Version Against Constraint in Go Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/Masterminds/semver/v3/README.md Demonstrates how to parse a constraint and version string, then validate the version against the constraint to retrieve specific error messages. ```go c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") if err != nil { // Handle constraint not being parseable. } v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parseable. } // Validate a version against a constraint. a, msgs := c.Validate(v) // a is false for _, m := range msgs { fmt.Println(m) // Loops over the errors which would read // "1.3 is greater than 1.2.3" // "1.3 is less than 1.4" } ``` -------------------------------- ### Manage Persistent Disk Lifecycle Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Handles creation with cloud-specific properties, deletion, existence checking, and resizing of persistent disks. Use `CreateDisk` for new disks, `DeleteDisk` to remove them, `HasDisk` to check for existence, and `ResizeDisk` to change their size. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) type DiskProps struct { Type string `json:"type"` // e.g., "ssd", "standard" IOPS int `json:"iops"` } func (c *MyCPI) CreateDisk( sizeMB int, cloudProps apiv1.DiskCloudProps, vmCID *apiv1.VMCID, // Optional hint for locality ) (apiv1.DiskCID, error) { var props DiskProps cloudProps.As(&props) // Use VM location hint for disk placement var zone string if vmCID != nil { zone, _ = c.client.GetInstanceZone(vmCID.AsString()) } diskID, err := c.client.CreateDisk(sizeMB, props.Type, zone) if err != nil { return apiv1.DiskCID{}, err } return apiv1.NewDiskCID(diskID), nil } func (c *MyCPI) DeleteDisk(cid apiv1.DiskCID) error { return c.client.DeleteDisk(cid.AsString()) } func (c *MyCPI) HasDisk(cid apiv1.DiskCID) (bool, error) { _, err := c.client.GetDisk(cid.AsString()) if err != nil { if isNotFoundError(err) { return false, nil } return false, err } return true, nil } func (c *MyCPI) ResizeDisk(cid apiv1.DiskCID, newSizeMB int) error { return c.client.ResizeDisk(cid.AsString(), newSizeMB) } func (c *MyCPI) SetDiskMetadata(cid apiv1.DiskCID, metadata apiv1.DiskMeta) error { return c.client.SetDiskTags(cid.AsString(), nil) } ``` -------------------------------- ### Implement CPI Interface Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Implement the main CPI interface to provide a complete cloud provider integration for BOSH. This includes methods for stemcell, VM, disk, and snapshot operations. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) // MyCPI implements the full apiv1.CPI interface type MyCPI struct { config Config client CloudClient // Your cloud provider client } // Ensure interface compliance at compile time var _ apiv1.CPI = &MyCPI{} // Info returns CPI capabilities func (c *MyCPI) Info() (apiv1.Info, error) { return apiv1.Info{ StemcellFormats: []string{"raw", "qcow2"}, // APIVersion is filled automatically by the library }, } // Example: Full interface requires implementing all methods from: // - StemcellsV1: CreateStemcell, DeleteStemcell // - VMsV1: CreateVM, DeleteVM, HasVM, RebootVM, SetVMMetadata, GetDisks, CalculateVMCloudProperties // - VMsV2Additions: CreateVMV2 (returns networks) // - DisksV1: CreateDisk, DeleteDisk, AttachDisk, DetachDisk, HasDisk, ResizeDisk, SetDiskMetadata // - DisksV2Additions: AttachDiskV2 (returns disk hint) // - SnapshotsV1: SnapshotDisk, DeleteSnapshot ``` -------------------------------- ### Define BDD-style test specifications with Ginkgo Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/onsi/ginkgo/v2/README.md Uses Describe, Context, and It blocks to structure test scenarios. Requires importing Ginkgo and Gomega packages and utilizes SpecContext for timeout management. ```go import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ... ) var _ = Describe("Checking books out of the library", Label("library"), func() { var library *libraries.Library var book *books.Book var valjean *users.User BeforeEach(func() { library = libraries.NewClient() book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", } valjean = users.NewUser("Jean Valjean") }) When("the library has the book in question", func() { BeforeEach(func(ctx SpecContext) { Expect(library.Store(ctx, book)).To(Succeed()) }) Context("and the book is available", func() { It("lends it to the reader", func(ctx SpecContext) { Expect(valjean.Checkout(ctx, library, "Les Miserables")).To(Succeed()) Expect(valjean.Books()).To(ContainElement(book)) Expect(library.UserWithBook(ctx, book)).To(Equal(valjean)) }, SpecTimeout(time.Second * 5)) }) Context("but the book has already been checked out", func() { var javert *users.User BeforeEach(func(ctx SpecContext) { javert = users.NewUser("Javert") Expect(javert.Checkout(ctx, library, "Les Miserables")).To(Succeed()) }) It("tells the user", func(ctx SpecContext) { err := valjean.Checkout(ctx, library, "Les Miserables") Expect(err).To(MatchError("Les Miserables is currently checked out")) }, SpecTimeout(time.Second * 5)) It("lets the user place a hold and get notified later", func(ctx SpecContext) { Expect(valjean.Hold(ctx, library, "Les Miserables")).To(Succeed()) Expect(valjean.Holds(ctx)).To(ContainElement(book)) By("when Javert returns the book") Expect(javert.Return(ctx, library, book)).To(Succeed()) By("it eventually informs Valjean") notification := "Les Miserables is ready for pick up" Eventually(ctx, valjean.Notifications).Should(ContainElement(notification)) Expect(valjean.Checkout(ctx, library, "Les Miserables")).To(Succeed()) Expect(valjean.Books(ctx)).To(ContainElement(book)) Expect(valjean.Holds(ctx)).To(BeEmpty()) }, SpecTimeout(time.Second * 10)) }) }) When("the library does not have the book in question", func() { It("tells the reader the book is unavailable", func(ctx SpecContext) { err := valjean.Checkout(ctx, library, "Les Miserables") Expect(err).To(MatchError("Les Miserables is not in the library catalog")) }, SpecTimeout(time.Second * 5)) }) }) ``` -------------------------------- ### Commit, Push, and Create GitHub Release Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/onsi/ginkgo/v2/RELEASING.md Commands to commit the version change, push to the repository, and create a new GitHub release. ```bash git commit -m "vM.m.p" git push gh release create "vM.m.p" git fetch --tags origin master ``` -------------------------------- ### Check Version Against Constraints Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/Masterminds/semver/v3/README.md Use NewConstraint to define range rules and NewVersion to parse version strings, then call Check to validate compatibility. ```go c, err := semver.NewConstraint(">= 1.2.3") if err != nil { // Handle constraint not being parsable. } v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parsable. } // Check if the version meets the constraints. The variable a will be true. a := c.Check(v) ``` -------------------------------- ### System Call Entry Points Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/golang.org/x/sys/unix/README.md These are the hand-written assembly entry points for system call dispatch in the sys/unix package. They differ in the number of arguments they can pass to the kernel. ```go func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) ``` ```go func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) ``` ```go func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) ``` -------------------------------- ### Manage Disk Snapshots Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Provides functionality for creating and deleting disk snapshots. Use `SnapshotDisk` for backups and `DeleteSnapshot` to remove them. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) func (c *MyCPI) SnapshotDisk( diskCID apiv1.DiskCID, metadata apiv1.DiskMeta, ) (apiv1.SnapshotCID, error) { snapshotID, err := c.client.CreateSnapshot(diskCID.AsString()) if err != nil { return apiv1.SnapshotCID{}, err } return apiv1.NewSnapshotCID(snapshotID), nil } func (c *MyCPI) DeleteSnapshot(cid apiv1.SnapshotCID) error { return c.client.DeleteSnapshot(cid.AsString()) } ``` -------------------------------- ### Update Agent Environment by Detaching Disk Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Shows how to parse an existing agent environment from bytes, detach a persistent disk using its CID, and then re-serialize the updated environment. This is useful for modifying VM configurations. ```go // Parsing existing agent env (e.g., when modifying) func (c *MyCPI) updateAgentEnv(existingBytes []byte, diskCID apiv1.DiskCID) ([]byte, error) { factory := apiv1.NewAgentEnvFactory() agentEnv, err := factory.FromBytes(existingBytes) if err != nil { return nil, err } // Detach a persistent disk agentEnv.DetachPersistentDisk(diskCID) return agentEnv.AsBytes() } ``` -------------------------------- ### Unmarshal and Marshal YAML Data in Go Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/go.yaml.in/yaml/v3/README.md Demonstrates unmarshaling YAML data into a Go struct and a map, and then marshaling them back into YAML format. Ensure struct fields are public for correct unmarshaling. ```go package main import ( "fmt" "log" "go.yaml.in/yaml/v3" ) var data = ` a: Easy! b: c: 2 d: [3, 4] ` // Note: struct fields must be public in order for unmarshal to // correctly populate the data. type T struct { A string B struct { RenamedC int `yaml:"c"` D []int `yaml:",flow"` } } func main() { t := T{} err := yaml.Unmarshal([]byte(data), &t) if err != nil { log.Fatalf("error: %v", err) } fmt.Printf("--- t:\n%v\n\n", t) d, err := yaml.Marshal(&t) if err != nil { log.Fatalf("error: %v", err) } fmt.Printf("--- t dump:\n%s\n\n", string(d)) m := make(map[interface{}]interface{}) err = yaml.Unmarshal([]byte(data), &m) if err != nil { log.Fatalf("error: %v", err) } fmt.Printf("--- m:\n%v\n\n", m) d, err = yaml.Marshal(&m) if err != nil { log.Fatalf("error: %v", err) } fmt.Printf("--- m dump:\n%s\n\n", string(d)) } ``` -------------------------------- ### Run Ginkgo Specs in Parallel Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/onsi/ginkgo/v2/README.md Use the '-p' flag with the ginkgo command to run your test suite in parallel. This is useful for speeding up execution of large integration suites. ```bash ginkgo -p ``` -------------------------------- ### CalculateVMCloudProperties Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Converts abstract VM resource requirements into cloud-specific instance types. ```APIDOC ## POST /api/v1/vm_properties/calculate ### Description Converts abstract VM resource requirements (RAM, CPU, disk size) into cloud-specific instance types and properties. ### Method POST ### Endpoint /api/v1/vm_properties/calculate ### Parameters #### Request Body - **resources** (apiv1.VMResources) - Required - The VM resource requirements. - **ram** (integer) - Required - RAM in MB. - **cpu** (integer) - Required - Number of CPU cores. - **ephemeral_disk_size** (integer) - Required - Ephemeral disk size in MB. ### Response #### Success Response (200) - **vm_cloud_props** (apiv1.VMCloudProps) - The calculated cloud-specific VM properties. #### Response Example { "vm_cloud_props": { "instance_type": "medium", "ephemeral_disk_size": 10240 } } ``` -------------------------------- ### Load Slim-Sprig FuncMap Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/go-task/slim-sprig/v3/README.md Register the Slim-Sprig function map with Go templates. The FuncMap must be applied before parsing templates. ```go import ( "html/template" "github.com/go-task/slim-sprig" ) // This example illustrates that the FuncMap *must* be set before the // templates themselves are loaded. tpl := template.Must( template.New("base").Funcs(sprig.FuncMap()).ParseGlob("*.html") ) ``` -------------------------------- ### Attach and Detach Disk Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Manages the attachment and detachment of disks to VMs. `AttachDiskV2` provides a disk hint for the BOSH agent to locate the disk, which can be a device path or a structured hint. ```go package main import ( "github.com/cloudfoundry/bosh-cpi-go/apiv1" ) func (c *MyCPI) AttachDisk(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) error { _, err := c.attachDiskInternal(vmCID, diskCID) return err } func (c *MyCPI) AttachDiskV2(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) (apiv1.DiskHint, error) { devicePath, err := c.attachDiskInternal(vmCID, diskCID) if err != nil { return apiv1.DiskHint{}, err } // Return disk hint as device path string return apiv1.NewDiskHintFromString(devicePath), nil // Or return structured hint with volume ID and device name // return apiv1.NewDiskHintFromMap(map[string]interface{}{ // "path": devicePath, // "volume_id": diskCID.AsString(), // }), nil } func (c *MyCPI) attachDiskInternal(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) (string, error) { devicePath, err := c.client.AttachDisk(vmCID.AsString(), diskCID.AsString()) if err != nil { return "", err } return devicePath, nil // e.g., "/dev/sdb" } func (c *MyCPI) DetachDisk(vmCID apiv1.VMCID, diskCID apiv1.DiskCID) error { return c.client.DetachDisk(vmCID.AsString(), diskCID.AsString()) } ``` -------------------------------- ### Handle Format Strings in Values Source: https://github.com/cloudfoundry/bosh-cpi-go/blob/master/vendor/github.com/go-logr/logr/README.md Pattern for cases where a format string is strictly necessary by using fmt.Sprintf within a key-value pair. ```go log.Printf("unable to reflect over type %T") ``` ```go logger.Info("unable to reflect over type", "type", fmt.Sprintf("%T")) ``` -------------------------------- ### Persistent Disk Lifecycle Management Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt APIs for creating, deleting, checking existence, and resizing persistent disks. ```APIDOC ## POST /api/disks ### Description Creates a new persistent disk with specified size and cloud properties. ### Method POST ### Endpoint /api/disks ### Parameters #### Query Parameters - **sizeMB** (integer) - Required - The size of the disk in megabytes. #### Request Body - **cloudProps** (object) - Required - Cloud-specific properties for disk creation (e.g., type, IOPS). - **type** (string) - Optional - The type of disk (e.g., "ssd", "standard"). - **iops** (integer) - Optional - The number of IOPS for the disk. ### Request Example ```json { "cloudProps": { "type": "ssd", "iops": 1000 } } ``` ### Response #### Success Response (200) - **diskCID** (string) - The unique identifier for the newly created disk. #### Response Example ```json { "diskCID": "disk-12345" } ``` ## DELETE /api/disks/{disk_cid} ### Description Deletes a persistent disk identified by its CID. ### Method DELETE ### Endpoint /api/disks/{disk_cid} ### Parameters #### Path Parameters - **disk_cid** (string) - Required - The CID of the disk to delete. ### Response #### Success Response (200) - (No content) ## GET /api/disks/{disk_cid} ### Description Checks if a persistent disk with the given CID exists. ### Method GET ### Endpoint /api/disks/{disk_cid} ### Parameters #### Path Parameters - **disk_cid** (string) - Required - The CID of the disk to check. ### Response #### Success Response (200) - **exists** (boolean) - True if the disk exists, false otherwise. #### Response Example ```json { "exists": true } ``` ## PUT /api/disks/{disk_cid}/resize ### Description Resizes a persistent disk to a new size. ### Method PUT ### Endpoint /api/disks/{disk_cid}/resize ### Parameters #### Path Parameters - **disk_cid** (string) - Required - The CID of the disk to resize. #### Request Body - **newSizeMB** (integer) - Required - The new size of the disk in megabytes. ### Request Example ```json { "newSizeMB": 20480 } ``` ### Response #### Success Response (200) - (No content) ``` -------------------------------- ### VM Lifecycle Operations Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt Provides basic VM lifecycle operations including deletion, existence checking, and rebooting. ```APIDOC ## DELETE /api/v1/vms/{vm_cid} ### Description Deletes a virtual machine. Disks attached to the VM are detached before deletion. ### Method DELETE ### Endpoint /api/v1/vms/{vm_cid} ### Parameters #### Path Parameters - **vm_cid** (apiv1.VMCID) - Required - The identifier of the virtual machine to delete. ## GET /api/v1/vms/{vm_cid} ### Description Checks if a virtual machine with the specified ID exists. ### Method GET ### Endpoint /api/v1/vms/{vm_cid} ### Parameters #### Path Parameters - **vm_cid** (apiv1.VMCID) - Required - The identifier of the virtual machine to check. ### Response #### Success Response (200) - **exists** (boolean) - True if the VM exists, false otherwise. #### Response Example { "exists": true } ## POST /api/v1/vms/{vm_cid}/reboot ### Description Reboots a virtual machine. ### Method POST ### Endpoint /api/v1/vms/{vm_cid}/reboot ### Parameters #### Path Parameters - **vm_cid** (apiv1.VMCID) - Required - The identifier of the virtual machine to reboot. ## PUT /api/v1/vms/{vm_cid}/metadata ### Description Sets metadata for a virtual machine. ### Method PUT ### Endpoint /api/v1/vms/{vm_cid}/metadata ### Parameters #### Path Parameters - **vm_cid** (apiv1.VMCID) - Required - The identifier of the virtual machine. #### Request Body - **metadata** (apiv1.VMMeta) - Required - A map of metadata key-value pairs. ## GET /api/v1/vms/{vm_cid}/disks ### Description Retrieves a list of disk CIDs attached to a virtual machine. ### Method GET ### Endpoint /api/v1/vms/{vm_cid}/disks ### Parameters #### Path Parameters - **vm_cid** (apiv1.VMCID) - Required - The identifier of the virtual machine. ### Response #### Success Response (200) - **disk_cids** (array of apiv1.DiskCID) - A list of disk identifiers attached to the VM. #### Response Example { "disk_cids": ["disk-abc", "disk-def"] } ``` -------------------------------- ### Disk Attachment and Detachment Source: https://context7.com/cloudfoundry/bosh-cpi-go/llms.txt APIs for attaching and detaching persistent disks to and from virtual machines. ```APIDOC ## POST /api/vms/{vm_cid}/attach_disk ### Description Attaches a persistent disk to a virtual machine. ### Method POST ### Endpoint /api/vms/{vm_cid}/attach_disk ### Parameters #### Path Parameters - **vm_cid** (string) - Required - The CID of the virtual machine. #### Request Body - **disk_cid** (string) - Required - The CID of the disk to attach. ### Request Example ```json { "disk_cid": "disk-12345" } ``` ### Response #### Success Response (200) - **device_path** (string) - The device path where the disk is attached (e.g., "/dev/sdb"). #### Response Example ```json { "device_path": "/dev/sdb" } ``` ## POST /api/vms/{vm_cid}/attach_disk_v2 ### Description Attaches a persistent disk to a virtual machine and returns a disk hint for the BOSH agent. ### Method POST ### Endpoint /api/vms/{vm_cid}/attach_disk_v2 ### Parameters #### Path Parameters - **vm_cid** (string) - Required - The CID of the virtual machine. #### Request Body - **disk_cid** (string) - Required - The CID of the disk to attach. ### Request Example ```json { "disk_cid": "disk-12345" } ``` ### Response #### Success Response (200) - **disk_hint** (object) - A hint for the BOSH agent to locate the attached disk. - **path** (string) - The device path (e.g., "/dev/sdb"). - **volume_id** (string) - The disk CID. #### Response Example ```json { "disk_hint": { "path": "/dev/sdb", "volume_id": "disk-12345" } } ``` ## DELETE /api/vms/{vm_cid}/detach_disk ### Description Detaches a persistent disk from a virtual machine. ### Method DELETE ### Endpoint /api/vms/{vm_cid}/detach_disk ### Parameters #### Path Parameters - **vm_cid** (string) - Required - The CID of the virtual machine. #### Request Body - **disk_cid** (string) - Required - The CID of the disk to detach. ### Request Example ```json { "disk_cid": "disk-12345" } ``` ### Response #### Success Response (200) - (No content) ```