1// Copyright 2016 The go-github AUTHORS. All rights reserved. 2// 3// Use of this source code is governed by a BSD-style 4// license that can be found in the LICENSE file. 5 6package github 7 8import ( 9 "context" 10 "errors" 11 "fmt" 12 "net/http" 13 "strings" 14) 15 16// MigrationService provides access to the migration related functions 17// in the GitHub API. 18// 19// GitHub API docs: https://developer.github.com/v3/migration/ 20type MigrationService service 21 22// Migration represents a GitHub migration (archival). 23type Migration struct { 24 ID *int64 `json:"id,omitempty"` 25 GUID *string `json:"guid,omitempty"` 26 // State is the current state of a migration. 27 // Possible values are: 28 // "pending" which means the migration hasn't started yet, 29 // "exporting" which means the migration is in progress, 30 // "exported" which means the migration finished successfully, or 31 // "failed" which means the migration failed. 32 State *string `json:"state,omitempty"` 33 // LockRepositories indicates whether repositories are locked (to prevent 34 // manipulation) while migrating data. 35 LockRepositories *bool `json:"lock_repositories,omitempty"` 36 // ExcludeAttachments indicates whether attachments should be excluded from 37 // the migration (to reduce migration archive file size). 38 ExcludeAttachments *bool `json:"exclude_attachments,omitempty"` 39 URL *string `json:"url,omitempty"` 40 CreatedAt *string `json:"created_at,omitempty"` 41 UpdatedAt *string `json:"updated_at,omitempty"` 42 Repositories []*Repository `json:"repositories,omitempty"` 43} 44 45func (m Migration) String() string { 46 return Stringify(m) 47} 48 49// MigrationOptions specifies the optional parameters to Migration methods. 50type MigrationOptions struct { 51 // LockRepositories indicates whether repositories should be locked (to prevent 52 // manipulation) while migrating data. 53 LockRepositories bool 54 55 // ExcludeAttachments indicates whether attachments should be excluded from 56 // the migration (to reduce migration archive file size). 57 ExcludeAttachments bool 58} 59 60// startMigration represents the body of a StartMigration request. 61type startMigration struct { 62 // Repositories is a slice of repository names to migrate. 63 Repositories []string `json:"repositories,omitempty"` 64 65 // LockRepositories indicates whether repositories should be locked (to prevent 66 // manipulation) while migrating data. 67 LockRepositories *bool `json:"lock_repositories,omitempty"` 68 69 // ExcludeAttachments indicates whether attachments should be excluded from 70 // the migration (to reduce migration archive file size). 71 ExcludeAttachments *bool `json:"exclude_attachments,omitempty"` 72} 73 74// StartMigration starts the generation of a migration archive. 75// repos is a slice of repository names to migrate. 76// 77// GitHub API docs: https://developer.github.com/v3/migration/migrations/#start-a-migration 78func (s *MigrationService) StartMigration(ctx context.Context, org string, repos []string, opt *MigrationOptions) (*Migration, *Response, error) { 79 u := fmt.Sprintf("orgs/%v/migrations", org) 80 81 body := &startMigration{Repositories: repos} 82 if opt != nil { 83 body.LockRepositories = Bool(opt.LockRepositories) 84 body.ExcludeAttachments = Bool(opt.ExcludeAttachments) 85 } 86 87 req, err := s.client.NewRequest("POST", u, body) 88 if err != nil { 89 return nil, nil, err 90 } 91 92 // TODO: remove custom Accept header when this API fully launches. 93 req.Header.Set("Accept", mediaTypeMigrationsPreview) 94 95 m := &Migration{} 96 resp, err := s.client.Do(ctx, req, m) 97 if err != nil { 98 return nil, resp, err 99 } 100 101 return m, resp, nil 102} 103 104// ListMigrations lists the most recent migrations. 105// 106// GitHub API docs: https://developer.github.com/v3/migration/migrations/#get-a-list-of-migrations 107func (s *MigrationService) ListMigrations(ctx context.Context, org string) ([]*Migration, *Response, error) { 108 u := fmt.Sprintf("orgs/%v/migrations", org) 109 110 req, err := s.client.NewRequest("GET", u, nil) 111 if err != nil { 112 return nil, nil, err 113 } 114 115 // TODO: remove custom Accept header when this API fully launches. 116 req.Header.Set("Accept", mediaTypeMigrationsPreview) 117 118 var m []*Migration 119 resp, err := s.client.Do(ctx, req, &m) 120 if err != nil { 121 return nil, resp, err 122 } 123 124 return m, resp, nil 125} 126 127// MigrationStatus gets the status of a specific migration archive. 128// id is the migration ID. 129// 130// GitHub API docs: https://developer.github.com/v3/migration/migrations/#get-the-status-of-a-migration 131func (s *MigrationService) MigrationStatus(ctx context.Context, org string, id int64) (*Migration, *Response, error) { 132 u := fmt.Sprintf("orgs/%v/migrations/%v", org, id) 133 134 req, err := s.client.NewRequest("GET", u, nil) 135 if err != nil { 136 return nil, nil, err 137 } 138 139 // TODO: remove custom Accept header when this API fully launches. 140 req.Header.Set("Accept", mediaTypeMigrationsPreview) 141 142 m := &Migration{} 143 resp, err := s.client.Do(ctx, req, m) 144 if err != nil { 145 return nil, resp, err 146 } 147 148 return m, resp, nil 149} 150 151// MigrationArchiveURL fetches a migration archive URL. 152// id is the migration ID. 153// 154// GitHub API docs: https://developer.github.com/v3/migration/migrations/#download-a-migration-archive 155func (s *MigrationService) MigrationArchiveURL(ctx context.Context, org string, id int64) (url string, err error) { 156 u := fmt.Sprintf("orgs/%v/migrations/%v/archive", org, id) 157 158 req, err := s.client.NewRequest("GET", u, nil) 159 if err != nil { 160 return "", err 161 } 162 163 // TODO: remove custom Accept header when this API fully launches. 164 req.Header.Set("Accept", mediaTypeMigrationsPreview) 165 166 s.client.clientMu.Lock() 167 defer s.client.clientMu.Unlock() 168 169 // Disable the redirect mechanism because AWS fails if the GitHub auth token is provided. 170 var loc string 171 saveRedirect := s.client.client.CheckRedirect 172 s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 173 loc = req.URL.String() 174 return errors.New("disable redirect") 175 } 176 defer func() { s.client.client.CheckRedirect = saveRedirect }() 177 178 _, err = s.client.Do(ctx, req, nil) // expect error from disable redirect 179 if err == nil { 180 return "", errors.New("expected redirect, none provided") 181 } 182 if !strings.Contains(err.Error(), "disable redirect") { 183 return "", err 184 } 185 return loc, nil 186} 187 188// DeleteMigration deletes a previous migration archive. 189// id is the migration ID. 190// 191// GitHub API docs: https://developer.github.com/v3/migration/migrations/#delete-a-migration-archive 192func (s *MigrationService) DeleteMigration(ctx context.Context, org string, id int64) (*Response, error) { 193 u := fmt.Sprintf("orgs/%v/migrations/%v/archive", org, id) 194 195 req, err := s.client.NewRequest("DELETE", u, nil) 196 if err != nil { 197 return nil, err 198 } 199 200 // TODO: remove custom Accept header when this API fully launches. 201 req.Header.Set("Accept", mediaTypeMigrationsPreview) 202 203 return s.client.Do(ctx, req, nil) 204} 205 206// UnlockRepo unlocks a repository that was locked for migration. 207// id is the migration ID. 208// You should unlock each migrated repository and delete them when the migration 209// is complete and you no longer need the source data. 210// 211// GitHub API docs: https://developer.github.com/v3/migration/migrations/#unlock-a-repository 212func (s *MigrationService) UnlockRepo(ctx context.Context, org string, id int64, repo string) (*Response, error) { 213 u := fmt.Sprintf("orgs/%v/migrations/%v/repos/%v/lock", org, id, repo) 214 215 req, err := s.client.NewRequest("DELETE", u, nil) 216 if err != nil { 217 return nil, err 218 } 219 220 // TODO: remove custom Accept header when this API fully launches. 221 req.Header.Set("Accept", mediaTypeMigrationsPreview) 222 223 return s.client.Do(ctx, req, nil) 224} 225