diff --git a/backend/scaffolder/app/server.go b/backend/scaffolder/app/server.go index 88b6111bdf..2227108c20 100644 --- a/backend/scaffolder/app/server.go +++ b/backend/scaffolder/app/server.go @@ -2,20 +2,53 @@ package app import ( "context" + "fmt" identity "github.com/spotify/backstage/backend/proto/identity/v1" pb "github.com/spotify/backstage/proto/scaffolder/v1" + "github.com/spotify/backstage/scaffolder/remote" "github.com/spotify/backstage/scaffolder/repository" ) // Server is the inventory Grpc server type Server struct { - Repository *repository.Repository + repository *repository.Repository + github *remote.Github } -// GetAllTemplates returns the local templatess -func (s *Server) GetAllTemplates(ctx context.Context, req *pb.Empty) (*pb.GetAllTemplatesReply, error) { +// NewServer creates a new server for with all the things +func NewServer() *Server { + return &Server{ + github: remote.NewGithubClient(), + } +} + +// Create scaffolds the repo in github and then will create push to the repository +func (s *Server) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateReply, error) { + repo := remote.Repository{ + Name: req.ComponentId, + Org: req.Org, + Private: req.Private, + } + + err := s.github.CreateRepository(repo) + + fmt.Println("Meta is") + fmt.Println(req.Metadata) + fmt.Println("error is") + fmt.Println(err) + + return nil, nil + // first create the repository with github + + // move the template into a temporary directory + + // use git bindings to add the remote with access token and push to the directory +} + +// 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 🤷‍♂️ - definitions, err := s.Repository.Load() + definitions, err := s.repository.Load() var templates []*pb.Template for _, definition := range definitions { @@ -34,7 +67,7 @@ func (s *Server) GetAllTemplates(ctx context.Context, req *pb.Empty) (*pb.GetAll templates = append(templates, template) } - return &pb.GetAllTemplatesReply{ + return &pb.ListTemplatesReply{ Templates: templates, }, err } diff --git a/backend/scaffolder/go.mod b/backend/scaffolder/go.mod index 76873ce771..b29a9b6ad3 100644 --- a/backend/scaffolder/go.mod +++ b/backend/scaffolder/go.mod @@ -8,7 +8,9 @@ replace github.com/spotify/backstage/backend/proto => ../proto require ( github.com/golang/protobuf v1.3.3 + github.com/google/go-github/v29 v29.0.2 github.com/spotify/backstage/backend/proto v0.0.0-00010101000000-000000000000 github.com/spotify/backstage/proto v0.0.0-00010101000000-000000000000 + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be google.golang.org/grpc v1.27.0 ) diff --git a/backend/scaffolder/go.sum b/backend/scaffolder/go.sum index 00cad08092..d280642a93 100644 --- a/backend/scaffolder/go.sum +++ b/backend/scaffolder/go.sum @@ -14,7 +14,13 @@ github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= +github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -25,6 +31,7 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/backend/scaffolder/main.go b/backend/scaffolder/main.go index 2187247d04..e875470b7f 100644 --- a/backend/scaffolder/main.go +++ b/backend/scaffolder/main.go @@ -6,7 +6,6 @@ import ( pb "github.com/spotify/backstage/proto/scaffolder/v1" "github.com/spotify/backstage/scaffolder/app" - "google.golang.org/grpc" ) @@ -21,7 +20,9 @@ func main() { } grpcServer := grpc.NewServer() - pb.RegisterScaffolderServer(grpcServer, &app.Server{}) + serverHandler := app.NewServer() + + pb.RegisterScaffolderServer(grpcServer, serverHandler) log.Println("Serving Scaffolder Service") grpcServer.Serve(lis) } diff --git a/backend/scaffolder/remote/github.go b/backend/scaffolder/remote/github.go new file mode 100644 index 0000000000..21d026b709 --- /dev/null +++ b/backend/scaffolder/remote/github.go @@ -0,0 +1,65 @@ +package remote + +import ( + "context" + "fmt" + gh "github.com/google/go-github/v29/github" + "golang.org/x/oauth2" + "os" +) + +// Github is the exported struct +type Github struct { + client *gh.Client + ctx *context.Context +} + +// NewGithubClient returns a new client with the correct access token enabled +func NewGithubClient() *Github { + accessToken := os.Getenv("BOSS_GH_ACCESS_TOKEN") + + if accessToken == "" { + fmt.Println("No BOSS_GH_ACCESS_TOKEN set. Cannot continue") + os.Exit(1) + } + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: accessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + client := gh.NewClient(tc) + + return &Github{ + client: client, + ctx: &ctx, + } +} + +// Repository holds the information of the created repo +type Repository struct { + Org string + Name string + Private bool +} + +// CreateRepository will create the repository in Github ready for use by the scaffolder +func (g *Github) CreateRepository(repo Repository) error { + ghRepo := &gh.Repository{ + Name: &repo.Name, + Private: &repo.Private, + } + + var org string + + if repo.Org != "" { + org = repo.Org + } + + created, response, err := g.client.Repositories.Create(*g.ctx, org, ghRepo) + + fmt.Println(created) + fmt.Println(response) + fmt.Println(err) + return nil +} diff --git a/backend/scaffolder/repository/repository.go b/backend/scaffolder/repository/repository.go index 1896cd0ce9..65e81eb942 100644 --- a/backend/scaffolder/repository/repository.go +++ b/backend/scaffolder/repository/repository.go @@ -45,5 +45,5 @@ func (s *Repository) Load() ([]*TemplateDefinition, error) { templateDefinitions = append(templateDefinitions, &definition) } - return templateDefinitions, err + return templateDefinitions, nil } diff --git a/proto/scaffolder/v1/scaffolder.proto b/proto/scaffolder/v1/scaffolder.proto index a1dbff29d3..7b185b7fcd 100644 --- a/proto/scaffolder/v1/scaffolder.proto +++ b/proto/scaffolder/v1/scaffolder.proto @@ -3,22 +3,41 @@ syntax = "proto3"; package spotify.backstage.scaffolder.v1; import "identity/v1/identity.proto"; +import "google/protobuf/struct.proto"; option go_package = "scaffolderv1"; service Scaffolder { - rpc GetAllTemplates(Empty) returns (GetAllTemplatesReply); + rpc ListTemplates(Empty) returns (ListTemplatesReply); + rpc Create(CreateRequest) returns (CreateReply); } message Empty {} -message GetAllTemplatesReply { +message ListTemplatesReply { repeated Template templates = 1; } -message Template { - string id = 1; - string name = 2; - string description = 3; - spotify.backstage.identity.v1.User user = 4; +message CreateReply { + string component_id = 1; + } + +message CreateRequest { + string template_id = 1; + string org = 2; + string component_id = 3; + bool private = 4; + + // here's the cookiecutter.json that is used for the request. + // make as a struct so that we can pass through the data in a nice way + // withouth having to mess around with stuff and special types. + google.protobuf.Struct metadata = 5; +} + +message Template { + string id = 1; + string name = 2; + string description = 3; + spotify.backstage.identity.v1.User user = 4; +} \ No newline at end of file