Newer
Older
package csbi
import (
"bytes"
"context"
"fmt"
"io/fs"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
"github.com/openconfig/ygot/genutil"
"github.com/openconfig/ygot/gogen"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
status "google.golang.org/grpc/status"
"gopkg.in/yaml.v3"
)
// write takes a ygen.Generatedcode struct and writes the Go code
// snippets contained within it to the io.Writer, w, provided as an argument.
// The output includes a package header which is generated.
func write(ctx context.Context, code *gogen.GeneratedCode, path string, sbiType spb.Type) error {
if err := os.Mkdir(path, 0755); err != nil {
if err.(*fs.PathError).Err.Error() != "file exists" { //nolint:errorlint
return err
}
}
switch sbiType {
case spb.Type_TYPE_PLUGIN:
return writePlugin(code, path)
case spb.Type_TYPE_CONTAINERISED:
return writeCsbi(ctx, code, path)
default:
return fmt.Errorf("invalid sbi type provided")
}
}
func removePort(ip net.Addr) (string, error) {
addr, ok := ip.(*net.TCPAddr)
if !ok {
return "", fmt.Errorf("invalid type assertion")
}
return addr.IP.String(), nil
}
func writeCsbi(ctx context.Context, code *gogen.GeneratedCode, path string) error {
p, ok := peer.FromContext(ctx)
if !ok || p == nil {
e := fmt.Errorf("no peer information in context %v", ctx)
log.Error(e)
return status.Errorf(codes.Aborted, "%v", e)
}
controller, err := removePort(p.Addr)
if err != nil {
log.Error(err)
return status.Errorf(codes.Aborted, "%v", err)
}
target := ctx.Value("target-address")
writerViper := viper.New()
writerViper.Set("uuid", path)
writerViper.Set("controller", net.JoinHostPort(controller, "55055"))
writerViper.Set("target", target)
if err := writerViper.WriteConfigAs(filepath.Join(path, ".csbi.toml")); err != nil {
return err
}
if err := copyFile(path, "csbi.go"); err != nil {
return err
}
if err := copyFile(path, gostructAdditionsName); err != nil {
return err
}
if err := copyFile(path, "go.mod"); err != nil {
return err
}
if err := copyFile(path, "go.sum"); err != nil {
return err
}
if err := copyFile(path, "Dockerfile"); err != nil {
return err
}
return writeGoStruct(path, code, spb.Type_TYPE_CONTAINERISED)
func writePlugin(code *gogen.GeneratedCode, path string) error {
if err := copyFile(path, gostructAdditionsName); err != nil {
return err
}
return writeGoStruct(path, code, spb.Type_TYPE_PLUGIN)
}
func copyFile(path, filename string) error {
var stderr bytes.Buffer
srcFile := filepath.Join("resources", filename)
dstFile := filepath.Join(path, filename)
cmd := exec.Command("cp", srcFile, dstFile)
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
log.Error(stderr.String())
return err
}
log.WithFields(log.Fields{
"source file": srcFile,
"dst file": dstFile,
}).Debugf("file copied")
return nil
}
func writeGoStruct(path string, code *gogen.GeneratedCode, t spb.Type) error {
file := filepath.Join(path, gostructName)
generatedCode := genutil.OpenFile(file)
defer genutil.SyncFile(generatedCode)
// Write the package header to the supplier writer.
fmt.Fprint(generatedCode, code.CommonHeader) //nolint:errcheck
fmt.Fprint(generatedCode, code.OneOffHeader) //nolint:errcheck
// Write the returned Go code out. First the Structs - which is the struct
// definitions for the generated YANG entity, followed by the enumerations.
for _, snippet := range code.Structs {
fmt.Fprintln(generatedCode, snippet.String()) //nolint:errcheck
}
for _, snippet := range code.Enums {
fmt.Fprintln(generatedCode, snippet) //nolint:errcheck
}
// Write the generated enumeration map out.
fmt.Fprintln(generatedCode, code.EnumMap) //nolint:errcheck
// Write the schema out if it was received.
if len(code.JSONSchemaCode) > 0 {
fmt.Fprintln(generatedCode, code.JSONSchemaCode) //nolint:errcheck
}
if len(code.EnumTypeMap) > 0 {
fmt.Fprintln(generatedCode, code.EnumTypeMap) //nolint:errcheck
}
if err := writeManifest(path, &Manifest{
Name: "basic",
Author: "goSDN-Team",
Version: "v1.0.0",
}); err != nil {
return err
}
return nil
}
// deprecated.
func writeCode(path string, code *gogen.GeneratedCode, t spb.Type) error {
code.CommonHeader = strings.TrimSuffix(code.CommonHeader, ")\n")
code.CommonHeader = code.CommonHeader + southboundImportAmendmend
sbiStructCopy := southboundStruct
if t == spb.Type_TYPE_CONTAINERISED {
sbiStructCopy.Methods = sbiStructCopy.Methods + southboundStructCsbiAmendmend
sbiStructCopy.Methods = sbiStructCopy.Methods + southboundStructPluginAmendmend
code.Structs = append(code.Structs, sbiStructCopy)
file := filepath.Join(path, gostructName)
generatedCode := genutil.OpenFile(file)
defer genutil.SyncFile(generatedCode)
// Write the package header to the supplier writer.
fmt.Fprint(generatedCode, code.CommonHeader) //nolint:errcheck
fmt.Fprint(generatedCode, code.OneOffHeader) //nolint:errcheck
// Write the returned Go code out. First the Structs - which is the struct
// definitions for the generated YANG entity, followed by the enumerations.
for _, snippet := range code.Structs {
fmt.Fprintln(generatedCode, snippet) //nolint:errcheck
}
for _, snippet := range code.Enums {
fmt.Fprintln(generatedCode, snippet) //nolint:errcheck
}
// Write the generated enumeration map out.
fmt.Fprintln(generatedCode, code.EnumMap) //nolint:errcheck
// Write the schema out if it was received.
if len(code.JSONSchemaCode) > 0 {
fmt.Fprintln(generatedCode, code.JSONSchemaCode) //nolint:errcheck
}
if len(code.EnumTypeMap) > 0 {
fmt.Fprintln(generatedCode, code.EnumTypeMap) //nolint:errcheck
}
if err := writeManifest(path, &Manifest{
Name: "basic",
Author: "goSDN-Team",
Version: "v1.0.0",
}); err != nil {
return err
}
return nil
}
// Manifest represents a csbi manifest.
type Manifest struct {
Name, Author, Version string
}
func writeManifest(path string, manifest *Manifest) error {
m, err := yaml.Marshal(manifest)
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(path, manifestFileName), m, 0644); err != nil {
return err
}
return nil
}