func newSlaveNode(resp http.ResponseWriter, req *http.Request) { // get the class and table number from the request // TODO: Validate the class ID and table number class := req.FormValue("class") table := req.FormValue("table") if class == "" || table == "" { log.Errorln("Invalid request") resp.WriteHeader(http.StatusBadRequest) return } var nodeInfo = pb.NewSlaveData{ Class: class, Table: table, } nodeInfoJSON, err := protojson.Marshal(&nodeInfo) if err != nil { log.Errorln("Failed to marshal node info into JSON:", err) resp.WriteHeader(http.StatusInternalServerError) return } // Generate random hostname for the new node nameInt, err := rand.Int(rand.Reader, big.NewInt(int64(math.Pow(32, hostnameLen+2)))) if err != nil { log.Errorln("Failed to generate random hostname:", err) resp.WriteHeader(http.StatusInternalServerError) return } // Encode the hostname in base32 name := base32.StdEncoding.EncodeToString(nameInt.Bytes())[:hostnameLen] hostname := fmt.Sprintf("%v.%v", name, clusterDomain) nodeInfo.Hostname = hostname log.Debugln(hostname, name, nameInt) pulumi.Run(func(ctx *pulumi.Context) error { dev, err := metal.NewDevice(ctx, name, &metal.DeviceArgs{ Hostname: pulumi.String(hostname), Metro: pulumi.String(metro), OperatingSystem: pulumi.String(slaveOS), ProjectId: pulumi.String(projectID), Plan: pulumi.String(slavePlan), // Make equinix store our node info for us CustomData: pulumi.String(string(nodeInfoJSON)), BillingCycle: pulumi.String("hourly"), }) if err != nil { log.Errorln("Error creating new Device: ", err) return err } /* To get this UUID manually: Managed to find it in the network logs in browser devtools, under a query to the packet.net api with virtual-networks, it's the first ID field, in case anyone else (or I) need to find that again It's also in the terraform state file, somewhere, apparently. This should be automated later */ vlan0, err := metal.GetVlan(ctx, admVlan, pulumi.ID("1a2ca742-9beb-48ef-831e-b1d362add9dc"), nil) if err != nil { log.Errorln("Error Getting admVlan: ", err) return err } // This is horrible, why does pulumi require their own types // I do not approve of this, don't do this. dev.ID().ToStringOutput().ApplyT(func(devid string) pulumi.Output { portattachment, err := metal.NewPortVlanAttachment(ctx, fmt.Sprintf("%v-%v", name, admVlan), &metal.PortVlanAttachmentArgs{ DeviceId: pulumi.String(devid), PortName: pulumi.String("bond0"), VlanVnid: vlan0.Vxlan, }) pulumi.Printf("Port attachment: %v", portattachment.URN()) // There's no good way to handle this error, so we just log it and continue. // If this happens more than once then there's probably an issue with the code above if err != nil { log.Errorln("Error creating PortVlanAttachment: ", err) } return nil }) // Get the IP address of the new node, eventually put it into DNS as well (or don't store it in the DB at all?) dev.Networks.ToDeviceNetworkArrayOutput().ApplyT(func(ips []metal.DeviceNetwork) pulumi.Output { for _, ip := range ips { parsedip := net.ParseIP(*ip.Address) if adminIPv6SubnetParsed.Contains(parsedip) { nodeInfo.IPAddr = parsedip.String() break } } return nil }) //log.Debugf("%+v", nodeInfo) // log.Debugf("%+v", vlan0) // log.Debugf("%+v", dev) //log.Debugln(pulumi.All(dev.URN().ToStringOutput(), vlan0.URN().ToStringOutput()).ApplyT(func(args []interface{}) string { // return fmt.Sprintf("URNS: %v %v", args...) // })) log.Errorf("%#v", pulumi.Sprintf("%#v", pulumi.Printf("URNs: %#v %#v", dev.URN().ToStringOutput(), vlan0.URN().ToStringOutput()))) dbout := db.Create(&nodeInfo) if dbout.Error != nil { log.Errorln("Error inserting node info into DB: ", err) return err } // While it is slightly redundant to not just merge this return with whatever function came before, // It's good practice so that we can handle errors in future functions without modifications. return nil }) }