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 } else { 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 }