diff --git a/backend/scaffolder/app/server.go b/backend/scaffolder/app/server.go index 24a5af326f..d22475d3a1 100644 --- a/backend/scaffolder/app/server.go +++ b/backend/scaffolder/app/server.go @@ -7,10 +7,10 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "log" - "os/exec" identity "github.com/spotify/backstage/backend/proto/identity/v1" pb "github.com/spotify/backstage/backend/proto/scaffolder/v1" + "github.com/spotify/backstage/scaffolder/cutter" "github.com/spotify/backstage/scaffolder/fs" "github.com/spotify/backstage/scaffolder/remote" "github.com/spotify/backstage/scaffolder/repository" @@ -21,6 +21,7 @@ type Server struct { repository *repository.Repository github *remote.Github fs *fs.Filesystem + cookie *cutter.Cutter } // NewServer creates a new server for with all the things @@ -34,38 +35,49 @@ func NewServer() *Server { func (s *Server) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateReply, error) { // first create the repository with github log.Printf("Creating repository for Component %s", req.ComponentId) - // repo := remote.Repository{ - // Name: req.ComponentId, - // Org: req.Org, - // Private: req.Private, - // } - - // if _, err := s.github.CreateRepository(repo); err != nil { - // return nil, status.Error(codes.Internal, fmt.Sprintf("Could not create repository %s/%s", req.Org, req.ComponentId)) - // } - - marshaller := &jsonpb.Marshaler{} - d, e := marshaller.MarshalToString(req.Metadata) + repo := remote.Repository{ + Name: req.ComponentId, + Org: req.Org, + Private: req.Private, + } + if _, err := s.github.CreateRepository(repo); err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("Could not create repository %s/%s", req.Org, req.ComponentId)) + } // move the template into a temporary directory tempFolder, err := s.fs.PrepareTemplate( fs.Template{ - ID: req.TemplateId, - Metadata: req.Metadata, + ID: req.TemplateId, }, ) + if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Could not prepare the template")) + return nil, status.Error(codes.Internal, "Could not prepare the template") } - log.Printf("Created temporary folder %s", tempFolder) - // TODO(blam): Probably need to wrap this in a timeout or something to avoid waiting to long - cmd := exec.Command("cookiecutter", "--no-input", "-v", tempFolder) - cmd.Dir = tempFolder - err := cmd.Run() + // get the optional metadatafields from the json + marshaler := &jsonpb.Marshaler{} + cutterMetadata, err := marshaler.MarshalToString(req.Metadata) - fmt.Println(output) - fmt.Println(err) + if err != nil { + return nil, status.Error(codes.Internal, "Could not marshal the cookiecutter metadata") + } + + cookieTemplate := cutter.CookieCutterTemplate{ + Path: tempFolder, + ComponentID: req.ComponentId, + Metadata: cutterMetadata, + } + + // create the cookicutter json + if err := s.cookie.WriteMetadata(cookieTemplate); err != nil { + return nil, status.Error(codes.Internal, "Could not write cookie metadata") + } + + // run the cookiecutter on the folder + if err := s.cookie.Run(cookieTemplate); err != nil { + return nil, status.Error(codes.Internal, "Failed to run the cookie cutter") + } // use git bindings to add the remote with access token and push to the directory return nil, nil @@ -73,7 +85,7 @@ func (s *Server) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateR // ListTemplates returns the local templatess func (s *Server) ListTemplates(ctx context.Context, req *pb.Empty) (*pb.ListTemplatesReply, error) { - // todo (blam): yes we currently read the disk on every load. but it's fine for now 🤷‍♂️ + // TODO(blam): yes we currently read the disk on every load. but it's fine for now 🤷‍♂️ definitions, err := s.repository.Load() var templates []*pb.Template @@ -82,6 +94,7 @@ func (s *Server) ListTemplates(ctx context.Context, req *pb.Empty) (*pb.ListTemp Id: definition.ID, Name: definition.Name, Description: definition.Description, + // need to actually call the idenity service here to get the // actual user and propgate back when needed. User: &identity.User{ diff --git a/backend/scaffolder/cutter/cutter.go b/backend/scaffolder/cutter/cutter.go index 43857ae99b..36180a4c87 100644 --- a/backend/scaffolder/cutter/cutter.go +++ b/backend/scaffolder/cutter/cutter.go @@ -1,13 +1,61 @@ package cutter +import ( + "encoding/json" + "fmt" + "log" + "os" + "os/exec" +) + // Cutter is the wrapper around cookiecutter -type Cutter interface { +type Cutter struct { } -type Template { - +// CookieCutterTemplate holds the template ID and metadata for copying +type CookieCutterTemplate struct { + Path string + Metadata string + ComponentID string } -// Run runs cookiecutter in the folder provided -func (c *Cutter) Run() error { +// WriteMetadata runs cookiecutter in the folder provided +func (c *Cutter) WriteMetadata(template CookieCutterTemplate) error { + var temporaryCookieCutter map[string]interface{} + err := json.Unmarshal([]byte(template.Metadata), &temporaryCookieCutter) + if err != nil { + return err + } + + temporaryCookieCutter["component_id"] = template.ComponentID + finalMetadata, _ := json.Marshal(temporaryCookieCutter) + + file, err := os.Create(fmt.Sprintf("%s/cookiecutter.json", template.Path)) + + if err != nil { + return err + } + + defer file.Close() + + _, err = file.Write(finalMetadata) + + return err +} + +// Run will run the cookie cutter +func (c *Cutter) Run(template CookieCutterTemplate) error { + // TODO(blam): Probably need to wrap this in a timeout or something to avoid waiting to long + log.Printf("Running cookiecutter on %s", template.Path) + log.Printf("With metadata %s", template.Metadata) + + cmd := exec.Command("cookiecutter", "--no-input", "-v", template.Path) + cmd.Dir = template.Path + output, err := cmd.Output() + + if err != nil { + log.Printf("Cookie cutter failed with output: %s", output) + } + + return err }