github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/repo/create/http.go (about)

     1  package create
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/cli/cli/api"
    11  )
    12  
    13  // repoCreateInput is input parameters for the repoCreate method
    14  type repoCreateInput struct {
    15  	Name                 string
    16  	HomepageURL          string
    17  	Description          string
    18  	Visibility           string
    19  	OwnerLogin           string
    20  	TeamSlug             string
    21  	TemplateRepositoryID string
    22  	HasIssuesEnabled     bool
    23  	HasWikiEnabled       bool
    24  	GitIgnoreTemplate    string
    25  	LicenseTemplate      string
    26  }
    27  
    28  // createRepositoryInputV3 is the payload for the repo create REST API
    29  type createRepositoryInputV3 struct {
    30  	Name              string `json:"name"`
    31  	HomepageURL       string `json:"homepage,omitempty"`
    32  	Description       string `json:"description,omitempty"`
    33  	IsPrivate         bool   `json:"private"`
    34  	Visibility        string `json:"visibility,omitempty"`
    35  	TeamID            uint64 `json:"team_id,omitempty"`
    36  	HasIssuesEnabled  bool   `json:"has_issues"`
    37  	HasWikiEnabled    bool   `json:"has_wiki"`
    38  	GitIgnoreTemplate string `json:"gitignore_template,omitempty"`
    39  	LicenseTemplate   string `json:"license_template,omitempty"`
    40  }
    41  
    42  // createRepositoryInput is the payload for the repo create GraphQL mutation
    43  type createRepositoryInput struct {
    44  	Name             string `json:"name"`
    45  	HomepageURL      string `json:"homepageUrl,omitempty"`
    46  	Description      string `json:"description,omitempty"`
    47  	Visibility       string `json:"visibility"`
    48  	OwnerID          string `json:"ownerId,omitempty"`
    49  	TeamID           string `json:"teamId,omitempty"`
    50  	HasIssuesEnabled bool   `json:"hasIssuesEnabled"`
    51  	HasWikiEnabled   bool   `json:"hasWikiEnabled"`
    52  }
    53  
    54  // cloneTemplateRepositoryInput is the payload for creating a repo from a template using GraphQL
    55  type cloneTemplateRepositoryInput struct {
    56  	Name         string `json:"name"`
    57  	Visibility   string `json:"visibility"`
    58  	Description  string `json:"description,omitempty"`
    59  	OwnerID      string `json:"ownerId"`
    60  	RepositoryID string `json:"repositoryId"`
    61  }
    62  
    63  // repoCreate creates a new GitHub repository
    64  func repoCreate(client *http.Client, hostname string, input repoCreateInput) (*api.Repository, error) {
    65  	isOrg := false
    66  	var ownerID string
    67  	var teamID string
    68  	var teamIDv3 uint64
    69  
    70  	apiClient := api.NewClientFromHTTP(client)
    71  
    72  	if input.TeamSlug != "" {
    73  		team, err := resolveOrganizationTeam(apiClient, hostname, input.OwnerLogin, input.TeamSlug)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  		teamIDv3 = team.ID
    78  		teamID = team.NodeID
    79  		ownerID = team.Organization.NodeID
    80  		isOrg = true
    81  	} else if input.OwnerLogin != "" {
    82  		owner, err := resolveOwner(apiClient, hostname, input.OwnerLogin)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		ownerID = owner.NodeID
    87  		isOrg = owner.IsOrganization()
    88  	}
    89  
    90  	if input.TemplateRepositoryID != "" {
    91  		var response struct {
    92  			CloneTemplateRepository struct {
    93  				Repository api.Repository
    94  			}
    95  		}
    96  
    97  		if ownerID == "" {
    98  			var err error
    99  			ownerID, err = api.CurrentUserID(apiClient, hostname)
   100  			if err != nil {
   101  				return nil, err
   102  			}
   103  		}
   104  
   105  		variables := map[string]interface{}{
   106  			"input": cloneTemplateRepositoryInput{
   107  				Name:         input.Name,
   108  				Description:  input.Description,
   109  				Visibility:   strings.ToUpper(input.Visibility),
   110  				OwnerID:      ownerID,
   111  				RepositoryID: input.TemplateRepositoryID,
   112  			},
   113  		}
   114  
   115  		err := apiClient.GraphQL(hostname, `
   116  		mutation CloneTemplateRepository($input: CloneTemplateRepositoryInput!) {
   117  			cloneTemplateRepository(input: $input) {
   118  				repository {
   119  					id
   120  					name
   121  					owner { login }
   122  					url
   123  				}
   124  			}
   125  		}
   126  		`, variables, &response)
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  
   131  		return api.InitRepoHostname(&response.CloneTemplateRepository.Repository, hostname), nil
   132  	}
   133  
   134  	if input.GitIgnoreTemplate != "" || input.LicenseTemplate != "" {
   135  		inputv3 := createRepositoryInputV3{
   136  			Name:              input.Name,
   137  			HomepageURL:       input.HomepageURL,
   138  			Description:       input.Description,
   139  			IsPrivate:         strings.EqualFold(input.Visibility, "PRIVATE"),
   140  			TeamID:            teamIDv3,
   141  			HasIssuesEnabled:  input.HasIssuesEnabled,
   142  			HasWikiEnabled:    input.HasWikiEnabled,
   143  			GitIgnoreTemplate: input.GitIgnoreTemplate,
   144  			LicenseTemplate:   input.LicenseTemplate,
   145  		}
   146  
   147  		path := "user/repos"
   148  		if isOrg {
   149  			path = fmt.Sprintf("orgs/%s/repos", input.OwnerLogin)
   150  			inputv3.Visibility = strings.ToLower(input.Visibility)
   151  		}
   152  
   153  		body := &bytes.Buffer{}
   154  		enc := json.NewEncoder(body)
   155  		if err := enc.Encode(inputv3); err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		repo, err := api.CreateRepoTransformToV4(apiClient, hostname, "POST", path, body)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		return repo, nil
   164  	}
   165  
   166  	var response struct {
   167  		CreateRepository struct {
   168  			Repository api.Repository
   169  		}
   170  	}
   171  
   172  	variables := map[string]interface{}{
   173  		"input": createRepositoryInput{
   174  			Name:             input.Name,
   175  			Description:      input.Description,
   176  			HomepageURL:      input.HomepageURL,
   177  			Visibility:       strings.ToUpper(input.Visibility),
   178  			OwnerID:          ownerID,
   179  			TeamID:           teamID,
   180  			HasIssuesEnabled: input.HasIssuesEnabled,
   181  			HasWikiEnabled:   input.HasWikiEnabled,
   182  		},
   183  	}
   184  
   185  	err := apiClient.GraphQL(hostname, `
   186  	mutation RepositoryCreate($input: CreateRepositoryInput!) {
   187  		createRepository(input: $input) {
   188  			repository {
   189  				id
   190  				name
   191  				owner { login }
   192  				url
   193  			}
   194  		}
   195  	}
   196  	`, variables, &response)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	return api.InitRepoHostname(&response.CreateRepository.Repository, hostname), nil
   202  }
   203  
   204  type ownerResponse struct {
   205  	NodeID string `json:"node_id"`
   206  	Type   string `json:"type"`
   207  }
   208  
   209  func (r *ownerResponse) IsOrganization() bool {
   210  	return r.Type == "Organization"
   211  }
   212  
   213  func resolveOwner(client *api.Client, hostname, orgName string) (*ownerResponse, error) {
   214  	var response ownerResponse
   215  	err := client.REST(hostname, "GET", fmt.Sprintf("users/%s", orgName), nil, &response)
   216  	return &response, err
   217  }
   218  
   219  type teamResponse struct {
   220  	ID           uint64 `json:"id"`
   221  	NodeID       string `json:"node_id"`
   222  	Organization struct {
   223  		NodeID string `json:"node_id"`
   224  	}
   225  }
   226  
   227  func resolveOrganizationTeam(client *api.Client, hostname, orgName, teamSlug string) (*teamResponse, error) {
   228  	var response teamResponse
   229  	err := client.REST(hostname, "GET", fmt.Sprintf("orgs/%s/teams/%s", orgName, teamSlug), nil, &response)
   230  	return &response, err
   231  }
   232  
   233  // listGitIgnoreTemplates uses API v3 here because gitignore template isn't supported by GraphQL yet.
   234  func listGitIgnoreTemplates(httpClient *http.Client, hostname string) ([]string, error) {
   235  	var gitIgnoreTemplates []string
   236  	client := api.NewClientFromHTTP(httpClient)
   237  	err := client.REST(hostname, "GET", "gitignore/templates", nil, &gitIgnoreTemplates)
   238  	if err != nil {
   239  		return []string{}, err
   240  	}
   241  	return gitIgnoreTemplates, nil
   242  }
   243  
   244  // listLicenseTemplates uses API v3 here because license template isn't supported by GraphQL yet.
   245  func listLicenseTemplates(httpClient *http.Client, hostname string) ([]api.License, error) {
   246  	var licenseTemplates []api.License
   247  	client := api.NewClientFromHTTP(httpClient)
   248  	err := client.REST(hostname, "GET", "licenses", nil, &licenseTemplates)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	return licenseTemplates, nil
   253  }