Skip to content

Commit 0a022ab

Browse files
author
OpenShift Bot
authored
Merge pull request #10869 from csrwng/newapp_gitcreds
Merged by openshift-bot
2 parents d942e98 + 3502601 commit 0a022ab

File tree

6 files changed

+284
-6
lines changed

6 files changed

+284
-6
lines changed

Diff for: pkg/generate/app/app.go

+2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ type SourceRef struct {
118118
DockerfileContents string
119119

120120
Binary bool
121+
122+
RequiresAuth bool
121123
}
122124

123125
func urlWithoutRef(url url.URL) string {

Diff for: pkg/generate/app/cmd/describe.go

+5
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ func describeBuildPipelineWithImage(out io.Writer, ref app.ComponentReference, p
199199
fmt.Fprintf(out, " * Use 'start-build' to trigger a new build\n")
200200
}
201201
}
202+
203+
if pipeline.Build.Source.RequiresAuth {
204+
fmt.Fprintf(out, " * WARNING: this source repository may require credentials.\n"+
205+
" Create a secret with your git credentials and use 'set build-secret' to assign it to the build config.\n")
206+
}
202207
}
203208
if pipeline.Deployment != nil {
204209
if pipeline.Deployment.AsTest {

Diff for: pkg/generate/app/sourcelookup.go

+65-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"fmt"
66
"io/ioutil"
77
"net/url"
8+
"os"
89
"path/filepath"
10+
"runtime"
911
"strings"
1012

1113
"github.com/docker/docker/builder/dockerfile/parser"
@@ -102,6 +104,8 @@ type SourceRepository struct {
102104
binary bool
103105

104106
forceAddDockerfile bool
107+
108+
requiresAuth bool
105109
}
106110

107111
// NewSourceRepository creates a reference to a local or remote source code repository from
@@ -213,6 +217,9 @@ func (r *SourceRepository) Detect(d Detector, dockerStrategy bool) error {
213217
if err != nil {
214218
return err
215219
}
220+
if err = r.DetectAuth(); err != nil {
221+
return err
222+
}
216223
return nil
217224
}
218225

@@ -240,9 +247,7 @@ func (r *SourceRepository) LocalPath() (string, error) {
240247
if r.localDir, err = ioutil.TempDir("", "gen"); err != nil {
241248
return "", err
242249
}
243-
localURL := r.url
244-
ref := localURL.Fragment
245-
localURL.Fragment = ""
250+
localURL, ref := cloneURLAndRef(&r.url)
246251
r.localDir, err = CloneAndCheckoutSources(gitRepo, localURL.String(), ref, r.localDir, r.contextDir)
247252
if err != nil {
248253
return "", err
@@ -251,6 +256,60 @@ func (r *SourceRepository) LocalPath() (string, error) {
251256
return r.localDir, nil
252257
}
253258

259+
func cloneURLAndRef(url *url.URL) (*url.URL, string) {
260+
localURL := *url
261+
ref := localURL.Fragment
262+
localURL.Fragment = ""
263+
return &localURL, ref
264+
}
265+
266+
// DetectAuth returns an error if the source repository cannot be cloned
267+
// without the current user's environment. The following changes are made to the
268+
// environment:
269+
// 1) The HOME directory is set to a temporary dir to avoid loading any settings in .gitconfig
270+
// 2) The GIT_SSH variable is set to /dev/null so the regular SSH keys are not used
271+
// (changing the HOME directory is not enough).
272+
// 3) GIT_CONFIG_NOSYSTEM prevents git from loading system-wide config
273+
// 4) GIT_ASKPASS to prevent git from prompting for a user/password
274+
func (r *SourceRepository) DetectAuth() error {
275+
url, ok, err := r.RemoteURL()
276+
if err != nil {
277+
return err
278+
}
279+
if !ok {
280+
return nil // No auth needed, we can't find a remote URL
281+
}
282+
tempHome, err := ioutil.TempDir("", "githome")
283+
if err != nil {
284+
return err
285+
}
286+
defer os.RemoveAll(tempHome)
287+
tempSrc, err := ioutil.TempDir("", "gen")
288+
if err != nil {
289+
return err
290+
}
291+
defer os.RemoveAll(tempSrc)
292+
env := []string{
293+
fmt.Sprintf("HOME=%s", tempHome),
294+
"GIT_SSH=/dev/null",
295+
"GIT_CONFIG_NOSYSTEM=true",
296+
"GIT_ASKPASS=true",
297+
}
298+
if runtime.GOOS == "windows" {
299+
env = append(env,
300+
fmt.Sprintf("ProgramData=%s", os.Getenv("ProgramData")),
301+
fmt.Sprintf("SystemRoot=%s", os.Getenv("SystemRoot")),
302+
)
303+
}
304+
gitRepo := git.NewRepositoryWithEnv(env)
305+
localURL, ref := cloneURLAndRef(url)
306+
_, err = CloneAndCheckoutSources(gitRepo, localURL.String(), ref, tempSrc, "")
307+
if err != nil {
308+
r.requiresAuth = true
309+
}
310+
return nil
311+
}
312+
254313
// RemoteURL returns the remote URL of the source repository
255314
func (r *SourceRepository) RemoteURL() (*url.URL, bool, error) {
256315
if r.remoteURL != nil {
@@ -473,8 +532,9 @@ func StrategyAndSourceForRepository(repo *SourceRepository, image *ImageRef) (*B
473532
IsDockerBuild: repo.IsDockerBuild(),
474533
}
475534
source := &SourceRef{
476-
Binary: repo.binary,
477-
Secrets: repo.secrets,
535+
Binary: repo.binary,
536+
Secrets: repo.secrets,
537+
RequiresAuth: repo.requiresAuth,
478538
}
479539

480540
if repo.sourceImage != nil {

Diff for: pkg/generate/app/test/fakegit.go

+8
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,11 @@ func (f *FakeGit) TimedListRemote(timeout time.Duration, url string, args ...str
9292
func (f *FakeGit) GetInfo(location string) (*git.SourceInfo, []error) {
9393
return nil, nil
9494
}
95+
96+
func (f *FakeGit) Add(location string, spec string) error {
97+
return nil
98+
}
99+
100+
func (f *FakeGit) Commit(location string, message string) error {
101+
return nil
102+
}

Diff for: pkg/generate/git/repository.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type Repository interface {
3434
SubmoduleUpdate(dir string, init, recursive bool) error
3535
Archive(dir, ref, format string, w io.Writer) error
3636
Init(dir string, bare bool) error
37+
Add(dir string, spec string) error
38+
Commit(dir string, message string) error
3739
AddRemote(dir string, name, url string) error
3840
AddLocalConfig(dir, name, value string) error
3941
ShowFormat(dir, commit, format string) (string, error)
@@ -299,7 +301,22 @@ func (r *repository) ShowFormat(location, ref, format string) (string, error) {
299301

300302
// Init initializes a new git repository in the provided location
301303
func (r *repository) Init(location string, bare bool) error {
302-
_, _, err := r.git("", "init", "--bare", location)
304+
args := []string{"init"}
305+
if bare {
306+
args = append(args, "--bare")
307+
}
308+
args = append(args, location)
309+
_, _, err := r.git("", args...)
310+
return err
311+
}
312+
313+
func (r *repository) Add(location, spec string) error {
314+
_, _, err := r.git(location, "add", spec)
315+
return err
316+
}
317+
318+
func (r *repository) Commit(location, message string) error {
319+
_, _, err := r.git(location, "commit", "-m", message)
303320
return err
304321
}
305322

Diff for: test/integration/newapp_test.go

+186
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"io"
77
"io/ioutil"
88
"net/http"
9+
"net/http/httptest"
10+
"net/url"
911
"os"
1012
"path/filepath"
1113
"reflect"
@@ -15,6 +17,9 @@ import (
1517
"testing"
1618
"time"
1719

20+
"github.com/AaronO/go-git-http"
21+
"github.com/AaronO/go-git-http/auth"
22+
"github.com/elazarl/goproxy"
1823
docker "github.com/fsouza/go-dockerclient"
1924
kapi "k8s.io/kubernetes/pkg/api"
2025
"k8s.io/kubernetes/pkg/api/errors"
@@ -32,6 +37,7 @@ import (
3237
"github.com/openshift/origin/pkg/generate/app/cmd"
3338
apptest "github.com/openshift/origin/pkg/generate/app/test"
3439
"github.com/openshift/origin/pkg/generate/dockerfile"
40+
"github.com/openshift/origin/pkg/generate/git"
3541
"github.com/openshift/origin/pkg/generate/source"
3642
imageapi "github.com/openshift/origin/pkg/image/api"
3743
templateapi "github.com/openshift/origin/pkg/template/api"
@@ -1643,6 +1649,186 @@ func TestNewAppBuildConfigEnvVarsAndSecrets(t *testing.T) {
16431649
}
16441650
}
16451651

1652+
func TestNewAppSourceAuthRequired(t *testing.T) {
1653+
1654+
tests := []struct {
1655+
name string
1656+
passwordProtected bool
1657+
useProxy bool
1658+
expectAuthRequired bool
1659+
}{
1660+
{
1661+
name: "no auth",
1662+
passwordProtected: false,
1663+
useProxy: false,
1664+
expectAuthRequired: false,
1665+
},
1666+
{
1667+
name: "basic auth",
1668+
passwordProtected: true,
1669+
useProxy: false,
1670+
expectAuthRequired: true,
1671+
},
1672+
{
1673+
name: "proxy required",
1674+
passwordProtected: false,
1675+
useProxy: true,
1676+
expectAuthRequired: true,
1677+
},
1678+
{
1679+
name: "basic auth and proxy required",
1680+
passwordProtected: true,
1681+
useProxy: true,
1682+
expectAuthRequired: true,
1683+
},
1684+
}
1685+
1686+
for _, test := range tests {
1687+
url := setupLocalGitRepo(t, test.passwordProtected, test.useProxy)
1688+
1689+
sourceRepo, err := app.NewSourceRepository(url)
1690+
if err != nil {
1691+
t.Fatalf("%v", err)
1692+
}
1693+
1694+
detector := app.SourceRepositoryEnumerator{
1695+
Detectors: source.DefaultDetectors,
1696+
Tester: dockerfile.NewTester(),
1697+
}
1698+
1699+
if err = sourceRepo.Detect(detector, true); err != nil {
1700+
t.Fatalf("%v", err)
1701+
}
1702+
1703+
_, sourceRef, err := app.StrategyAndSourceForRepository(sourceRepo, nil)
1704+
if err != nil {
1705+
t.Fatalf("%v", err)
1706+
}
1707+
1708+
if test.expectAuthRequired != sourceRef.RequiresAuth {
1709+
t.Errorf("%s: unexpected auth required result. Expected: %v. Actual: %v", test.name, test.expectAuthRequired, sourceRef.RequiresAuth)
1710+
}
1711+
}
1712+
}
1713+
1714+
func setupLocalGitRepo(t *testing.T, passwordProtected bool, requireProxy bool) string {
1715+
// Create test directories
1716+
testDir, err := ioutil.TempDir("", "gitauth")
1717+
if err != nil {
1718+
t.Fatalf("%v", err)
1719+
}
1720+
initialRepoDir := filepath.Join(testDir, "initial-repo")
1721+
if err = os.Mkdir(initialRepoDir, 0755); err != nil {
1722+
t.Fatalf("%v", err)
1723+
}
1724+
gitHomeDir := filepath.Join(testDir, "git-home")
1725+
if err = os.Mkdir(gitHomeDir, 0755); err != nil {
1726+
t.Fatalf("%v", err)
1727+
}
1728+
testRepoDir := filepath.Join(gitHomeDir, "test-repo")
1729+
if err = os.Mkdir(testRepoDir, 0755); err != nil {
1730+
t.Fatalf("%v", err)
1731+
}
1732+
userHomeDir := filepath.Join(testDir, "user-home")
1733+
if err = os.Mkdir(userHomeDir, 0755); err != nil {
1734+
t.Fatalf("%v", err)
1735+
}
1736+
1737+
// Set initial repo contents
1738+
gitRepo := git.NewRepository()
1739+
if err = gitRepo.Init(initialRepoDir, false); err != nil {
1740+
t.Fatalf("%v", err)
1741+
}
1742+
if err = ioutil.WriteFile(filepath.Join(initialRepoDir, "Dockerfile"), []byte("FROM mysql\nLABEL mylabel=myvalue\n"), 0644); err != nil {
1743+
t.Fatalf("%v", err)
1744+
}
1745+
if err = gitRepo.Add(initialRepoDir, "."); err != nil {
1746+
t.Fatalf("%v", err)
1747+
}
1748+
if err = gitRepo.Commit(initialRepoDir, "initial commit"); err != nil {
1749+
t.Fatalf("%v", err)
1750+
}
1751+
1752+
// Clone to repository inside gitHomeDir
1753+
if err = gitRepo.CloneBare(testRepoDir, initialRepoDir); err != nil {
1754+
t.Fatalf("%v", err)
1755+
}
1756+
1757+
// Initialize test git server
1758+
var gitHandler http.Handler
1759+
gitHandler = githttp.New(gitHomeDir)
1760+
1761+
// If password protected, set handler to require password
1762+
user := "gituser"
1763+
password := "gitpass"
1764+
if passwordProtected {
1765+
authenticator := auth.Authenticator(func(info auth.AuthInfo) (bool, error) {
1766+
if info.Username != user && info.Password != password {
1767+
return false, nil
1768+
}
1769+
return true, nil
1770+
})
1771+
gitHandler = authenticator(gitHandler)
1772+
}
1773+
gitServer := httptest.NewServer(gitHandler)
1774+
gitURLString := fmt.Sprintf("%s/%s", gitServer.URL, "test-repo")
1775+
1776+
var proxyServer *httptest.Server
1777+
1778+
// If proxy required, create a simple proxy server that will forward any host to the git server
1779+
if requireProxy {
1780+
gitURL, err := url.Parse(gitURLString)
1781+
if err != nil {
1782+
t.Fatalf("%v", err)
1783+
}
1784+
proxy := goproxy.NewProxyHttpServer()
1785+
proxy.OnRequest().DoFunc(
1786+
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
1787+
r.URL.Host = gitURL.Host
1788+
return r, nil
1789+
})
1790+
gitURLString = "http://example.com/test-repo"
1791+
proxyServer = httptest.NewServer(proxy)
1792+
}
1793+
1794+
gitConfig := `
1795+
[user]
1796+
name = developer
1797+
1798+
`
1799+
if passwordProtected {
1800+
authSection := `
1801+
[url %q]
1802+
insteadOf = %s
1803+
`
1804+
urlWithAuth, err := url.Parse(gitURLString)
1805+
if err != nil {
1806+
t.Fatalf("%v", err)
1807+
}
1808+
urlWithAuth.User = url.UserPassword(user, password)
1809+
authSection = fmt.Sprintf(authSection, urlWithAuth.String(), gitURLString)
1810+
gitConfig += authSection
1811+
}
1812+
1813+
if requireProxy {
1814+
proxySection := `
1815+
[http]
1816+
proxy = %s
1817+
`
1818+
proxySection = fmt.Sprintf(proxySection, proxyServer.URL)
1819+
gitConfig += proxySection
1820+
}
1821+
1822+
if err = ioutil.WriteFile(filepath.Join(userHomeDir, ".gitconfig"), []byte(gitConfig), 0644); err != nil {
1823+
t.Fatalf("%v", err)
1824+
}
1825+
os.Setenv("HOME", userHomeDir)
1826+
os.Setenv("GIT_ASKPASS", "true")
1827+
1828+
return gitURLString
1829+
1830+
}
1831+
16461832
func builderImageStream() *imageapi.ImageStream {
16471833
return &imageapi.ImageStream{
16481834
ObjectMeta: kapi.ObjectMeta{

0 commit comments

Comments
 (0)