77 "strconv"
88 "strings"
99
10+ config "github.com/conductorone/baton-sdk/pb/c1/config/v1"
1011 v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
12+ "github.com/conductorone/baton-sdk/pkg/actions"
1113 "github.com/conductorone/baton-sdk/pkg/annotations"
1214 "github.com/conductorone/baton-sdk/pkg/pagination"
1315 "github.com/conductorone/baton-sdk/pkg/types/entitlement"
@@ -18,6 +20,7 @@ import (
1820 "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
1921 "go.uber.org/zap"
2022 "google.golang.org/grpc/codes"
23+ "google.golang.org/protobuf/types/known/structpb"
2124)
2225
2326// outside collaborators are given one of these roles too.
@@ -433,3 +436,363 @@ func skipGrantsForResourceType(bag *pagination.Bag) (string, error) {
433436 }
434437 return pageToken , nil
435438}
439+
440+ // ResourceActions registers the resource actions for the repository resource type.
441+ // This implements the ResourceActionProvider interface.
442+ func (o * repositoryResourceType ) ResourceActions (ctx context.Context , registry actions.ResourceTypeActionRegistry ) error {
443+ if err := o .registerCreateRepositoryAction (ctx , registry ); err != nil {
444+ return err
445+ }
446+ if err := o .registerDeleteRepositoryAction (ctx , registry ); err != nil {
447+ return err
448+ }
449+ return nil
450+ }
451+
452+ func (o * repositoryResourceType ) registerCreateRepositoryAction (ctx context.Context , registry actions.ResourceTypeActionRegistry ) error {
453+ return registry .Register (ctx , & v2.ResourceActionSchema {
454+ Name : "create" ,
455+ DisplayName : "Create Repository" ,
456+ Description : "Create a new repository in a GitHub organization" ,
457+ ActionType : []v2.ActionType {v2 .ActionType_ACTION_TYPE_RESOURCE_CREATE },
458+ Arguments : []* config.Field {
459+ {
460+ Name : "name" ,
461+ DisplayName : "Repository Name" ,
462+ Description : "The name of the repository to create" ,
463+ Field : & config.Field_StringField {},
464+ IsRequired : true ,
465+ },
466+ {
467+ Name : "parent" ,
468+ DisplayName : "Parent Organization" ,
469+ Description : "The organization to create the repository in" ,
470+ Field : & config.Field_ResourceIdField {},
471+ IsRequired : true ,
472+ },
473+ {
474+ Name : "description" ,
475+ DisplayName : "Description" ,
476+ Description : "A description of the repository" ,
477+ Field : & config.Field_StringField {},
478+ },
479+ {
480+ Name : "private" ,
481+ DisplayName : "Private" ,
482+ Description : "Whether the repository should be private (true/false)" ,
483+ Field : & config.Field_BoolField {},
484+ },
485+ {
486+ Name : "visibility" ,
487+ DisplayName : "Visibility" ,
488+ Description : "The visibility level: 'public', 'private', or 'internal'" ,
489+ Field : & config.Field_StringField {},
490+ },
491+ {
492+ Name : "has_issues" ,
493+ DisplayName : "Has Issues" ,
494+ Description : "Enable issues for this repository (true/false)" ,
495+ Field : & config.Field_BoolField {},
496+ },
497+ {
498+ Name : "has_projects" ,
499+ DisplayName : "Has Projects" ,
500+ Description : "Enable projects for this repository (true/false)" ,
501+ Field : & config.Field_BoolField {},
502+ },
503+ {
504+ Name : "has_wiki" ,
505+ DisplayName : "Has Wiki" ,
506+ Description : "Enable wiki for this repository (true/false)" ,
507+ Field : & config.Field_BoolField {},
508+ },
509+ {
510+ Name : "has_discussions" ,
511+ DisplayName : "Has Discussions" ,
512+ Description : "Enable discussions for this repository (true/false)" ,
513+ Field : & config.Field_BoolField {},
514+ },
515+ {
516+ Name : "auto_init" ,
517+ DisplayName : "Auto Initialize" ,
518+ Description : "Create an initial commit with empty README (true/false)" ,
519+ Field : & config.Field_BoolField {},
520+ },
521+ {
522+ Name : "gitignore_template" ,
523+ DisplayName : "Gitignore Template" ,
524+ Description : "Gitignore template to apply (e.g., 'Go', 'Python', 'Node')" ,
525+ Field : & config.Field_StringField {},
526+ },
527+ {
528+ Name : "license_template" ,
529+ DisplayName : "License Template" ,
530+ Description : "License template to apply (e.g., 'mit', 'apache-2.0', 'gpl-3.0')" ,
531+ Field : & config.Field_StringField {},
532+ },
533+ {
534+ Name : "allow_squash_merge" ,
535+ DisplayName : "Allow Squash Merge" ,
536+ Description : "Allow squash-merging pull requests (true/false)" ,
537+ Field : & config.Field_BoolField {},
538+ },
539+ {
540+ Name : "allow_merge_commit" ,
541+ DisplayName : "Allow Merge Commit" ,
542+ Description : "Allow merging pull requests with a merge commit (true/false)" ,
543+ Field : & config.Field_BoolField {},
544+ },
545+ {
546+ Name : "allow_rebase_merge" ,
547+ DisplayName : "Allow Rebase Merge" ,
548+ Description : "Allow rebase-merging pull requests (true/false)" ,
549+ Field : & config.Field_BoolField {},
550+ },
551+ {
552+ Name : "allow_auto_merge" ,
553+ DisplayName : "Allow Auto Merge" ,
554+ Description : "Allow auto-merge on pull requests (true/false)" ,
555+ Field : & config.Field_BoolField {},
556+ },
557+ {
558+ Name : "delete_branch_on_merge" ,
559+ DisplayName : "Delete Branch on Merge" ,
560+ Description : "Automatically delete head branches after pull requests are merged (true/false)" ,
561+ Field : & config.Field_BoolField {},
562+ },
563+ {
564+ Name : "is_template" ,
565+ DisplayName : "Is Template" ,
566+ Description : "Make this repository available as a template (true/false)" ,
567+ Field : & config.Field_BoolField {},
568+ },
569+ },
570+ ReturnTypes : []* config.Field {
571+ {Name : "success" , Field : & config.Field_BoolField {}},
572+ {Name : "resource" , Field : & config.Field_ResourceField {}},
573+ },
574+ }, o .handleCreateRepositoryAction )
575+ }
576+
577+ func (o * repositoryResourceType ) registerDeleteRepositoryAction (ctx context.Context , registry actions.ResourceTypeActionRegistry ) error {
578+ return registry .Register (ctx , & v2.ResourceActionSchema {
579+ Name : "delete" ,
580+ DisplayName : "Delete Repository" ,
581+ Description : "Delete a repository from a GitHub organization" ,
582+ ActionType : []v2.ActionType {v2 .ActionType_ACTION_TYPE_RESOURCE_DELETE },
583+ Arguments : []* config.Field {
584+ {
585+ Name : "resource" ,
586+ DisplayName : "Repository Resource" ,
587+ Description : "The repository resource to delete" ,
588+ Field : & config.Field_ResourceIdField {},
589+ IsRequired : true ,
590+ },
591+ {
592+ Name : "parent" ,
593+ DisplayName : "Parent Organization" ,
594+ Description : "The organization the repository belongs to" ,
595+ Field : & config.Field_ResourceIdField {},
596+ IsRequired : true ,
597+ },
598+ },
599+ ReturnTypes : []* config.Field {
600+ {Name : "success" , Field : & config.Field_BoolField {}},
601+ },
602+ }, o .handleDeleteRepositoryAction )
603+ }
604+
605+ func (o * repositoryResourceType ) handleCreateRepositoryAction (ctx context.Context , args * structpb.Struct ) (* structpb.Struct , annotations.Annotations , error ) {
606+ l := ctxzap .Extract (ctx )
607+
608+ // Extract required arguments using SDK helpers
609+ name , err := actions .RequireStringArg (args , "name" )
610+ if err != nil {
611+ return nil , nil , err
612+ }
613+
614+ parentResourceID , err := actions .RequireResourceIDArg (args , "parent" )
615+ if err != nil {
616+ return nil , nil , err
617+ }
618+
619+ // Get the organization name from the parent resource ID
620+ orgName , err := o .orgCache .GetOrgName (ctx , parentResourceID )
621+ if err != nil {
622+ return nil , nil , fmt .Errorf ("failed to get organization name: %w" , err )
623+ }
624+
625+ l .Info ("github-connector: creating repository via action" ,
626+ zap .String ("repo_name" , name ),
627+ zap .String ("org_name" , orgName ),
628+ )
629+
630+ // Build the Repository request
631+ newRepo := & github.Repository {
632+ Name : github .Ptr (name ),
633+ }
634+
635+ // Extract optional fields using SDK helpers
636+ if description , ok := actions .GetStringArg (args , "description" ); ok && description != "" {
637+ newRepo .Description = github .Ptr (description )
638+ }
639+
640+ if private , ok := actions .GetBoolArg (args , "private" ); ok {
641+ newRepo .Private = github .Ptr (private )
642+ }
643+
644+ if visibility , ok := actions .GetStringArg (args , "visibility" ); ok && visibility != "" {
645+ if visibility == "public" || visibility == "private" || visibility == "internal" {
646+ newRepo .Visibility = github .Ptr (visibility )
647+ } else {
648+ l .Warn ("github-connector: invalid visibility value, using default" ,
649+ zap .String ("provided_visibility" , visibility ),
650+ )
651+ }
652+ }
653+
654+ if hasIssues , ok := actions .GetBoolArg (args , "has_issues" ); ok {
655+ newRepo .HasIssues = github .Ptr (hasIssues )
656+ }
657+
658+ if hasProjects , ok := actions .GetBoolArg (args , "has_projects" ); ok {
659+ newRepo .HasProjects = github .Ptr (hasProjects )
660+ }
661+
662+ if hasWiki , ok := actions .GetBoolArg (args , "has_wiki" ); ok {
663+ newRepo .HasWiki = github .Ptr (hasWiki )
664+ }
665+
666+ if hasDiscussions , ok := actions .GetBoolArg (args , "has_discussions" ); ok {
667+ newRepo .HasDiscussions = github .Ptr (hasDiscussions )
668+ }
669+
670+ if autoInit , ok := actions .GetBoolArg (args , "auto_init" ); ok {
671+ newRepo .AutoInit = github .Ptr (autoInit )
672+ }
673+
674+ if gitignoreTemplate , ok := actions .GetStringArg (args , "gitignore_template" ); ok && gitignoreTemplate != "" {
675+ newRepo .GitignoreTemplate = github .Ptr (gitignoreTemplate )
676+ }
677+
678+ if licenseTemplate , ok := actions .GetStringArg (args , "license_template" ); ok && licenseTemplate != "" {
679+ newRepo .LicenseTemplate = github .Ptr (licenseTemplate )
680+ }
681+
682+ if allowSquashMerge , ok := actions .GetBoolArg (args , "allow_squash_merge" ); ok {
683+ newRepo .AllowSquashMerge = github .Ptr (allowSquashMerge )
684+ }
685+
686+ if allowMergeCommit , ok := actions .GetBoolArg (args , "allow_merge_commit" ); ok {
687+ newRepo .AllowMergeCommit = github .Ptr (allowMergeCommit )
688+ }
689+
690+ if allowRebaseMerge , ok := actions .GetBoolArg (args , "allow_rebase_merge" ); ok {
691+ newRepo .AllowRebaseMerge = github .Ptr (allowRebaseMerge )
692+ }
693+
694+ if allowAutoMerge , ok := actions .GetBoolArg (args , "allow_auto_merge" ); ok {
695+ newRepo .AllowAutoMerge = github .Ptr (allowAutoMerge )
696+ }
697+
698+ if deleteBranchOnMerge , ok := actions .GetBoolArg (args , "delete_branch_on_merge" ); ok {
699+ newRepo .DeleteBranchOnMerge = github .Ptr (deleteBranchOnMerge )
700+ }
701+
702+ if isTemplate , ok := actions .GetBoolArg (args , "is_template" ); ok {
703+ newRepo .IsTemplate = github .Ptr (isTemplate )
704+ }
705+
706+ // Create the repository via GitHub API
707+ createdRepo , resp , err := o .client .Repositories .Create (ctx , orgName , newRepo )
708+ if err != nil {
709+ return nil , nil , wrapGitHubError (err , resp , fmt .Sprintf ("failed to create repository %s in org %s" , name , orgName ))
710+ }
711+
712+ // Extract rate limit data for annotations
713+ var annos annotations.Annotations
714+ if rateLimitData , err := extractRateLimitData (resp ); err == nil {
715+ annos .WithRateLimiting (rateLimitData )
716+ }
717+
718+ l .Info ("github-connector: repository created successfully via action" ,
719+ zap .String ("repo_name" , createdRepo .GetName ()),
720+ zap .Int64 ("repo_id" , createdRepo .GetID ()),
721+ zap .String ("repo_full_name" , createdRepo .GetFullName ()),
722+ )
723+
724+ // Create the resource representation of the newly created repository
725+ repoResource , err := repositoryResource (ctx , createdRepo , parentResourceID )
726+ if err != nil {
727+ return nil , annos , fmt .Errorf ("failed to create resource representation: %w" , err )
728+ }
729+
730+ // Build return values using SDK helpers
731+ resourceRv , err := actions .NewResourceReturnField ("resource" , repoResource )
732+ if err != nil {
733+ return nil , annos , err
734+ }
735+
736+ return actions .NewReturnValues (true , resourceRv ), annos , nil
737+ }
738+
739+ func (o * repositoryResourceType ) handleDeleteRepositoryAction (ctx context.Context , args * structpb.Struct ) (* structpb.Struct , annotations.Annotations , error ) {
740+ l := ctxzap .Extract (ctx )
741+
742+ // Extract the repository resource ID using SDK helper
743+ resourceID , err := actions .RequireResourceIDArg (args , "resource" )
744+ if err != nil {
745+ return nil , nil , err
746+ }
747+
748+ // Extract the parent org resource ID using SDK helper
749+ parentResourceID , err := actions .RequireResourceIDArg (args , "parent" )
750+ if err != nil {
751+ return nil , nil , err
752+ }
753+
754+ // Parse the repo ID from the resource
755+ repoID , err := strconv .ParseInt (resourceID .Resource , 10 , 64 )
756+ if err != nil {
757+ return nil , nil , fmt .Errorf ("invalid repository ID %s: %w" , resourceID .Resource , err )
758+ }
759+
760+ // Get the organization name from the parent resource ID
761+ orgName , err := o .orgCache .GetOrgName (ctx , parentResourceID )
762+ if err != nil {
763+ return nil , nil , fmt .Errorf ("failed to get organization name: %w" , err )
764+ }
765+
766+ // First, get the repository to find its name (needed for deletion)
767+ repo , resp , err := o .client .Repositories .GetByID (ctx , repoID )
768+ if err != nil {
769+ return nil , nil , wrapGitHubError (err , resp , fmt .Sprintf ("failed to get repository %d" , repoID ))
770+ }
771+
772+ repoName := repo .GetName ()
773+
774+ l .Info ("github-connector: deleting repository via action" ,
775+ zap .Int64 ("repo_id" , repoID ),
776+ zap .String ("repo_name" , repoName ),
777+ zap .String ("org_name" , orgName ),
778+ )
779+
780+ // Delete the repository via GitHub API
781+ resp , err = o .client .Repositories .Delete (ctx , orgName , repoName )
782+ if err != nil {
783+ return nil , nil , wrapGitHubError (err , resp , fmt .Sprintf ("failed to delete repository %s in org %s" , repoName , orgName ))
784+ }
785+
786+ var annos annotations.Annotations
787+ if rateLimitData , err := extractRateLimitData (resp ); err == nil {
788+ annos .WithRateLimiting (rateLimitData )
789+ }
790+
791+ l .Info ("github-connector: repository deleted successfully via action" ,
792+ zap .Int64 ("repo_id" , repoID ),
793+ zap .String ("repo_name" , repoName ),
794+ zap .String ("org_name" , orgName ),
795+ )
796+
797+ return actions .NewReturnValues (true ), annos , nil
798+ }
0 commit comments