Skip to content

Commit

Permalink
Merge pull request #16 from paketo-buildpacks/netrc
Browse files Browse the repository at this point in the history
netrc
  • Loading branch information
ekcasey authored Oct 20, 2020
2 parents 2bd382b + 0b3069b commit 38155c5
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 25 deletions.
1 change: 1 addition & 0 deletions carton/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestUnit(t *testing.T) {
suite("BuildpackDependency", testBuildpackDependency)
suite("BuildImageDependency", testBuildImageDependency)
suite("LifecycleDependency", testLifecycleDependency)
suite("Netrc", testNetrc)
suite("Package", testPackage)
suite("PackageDependency", testPackageDependency)
suite.Run(t)
Expand Down
118 changes: 118 additions & 0 deletions carton/netrc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package carton

import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/user"
"path/filepath"
"strings"
)

type Netrc []NetrcLine

type NetrcLine struct {
Machine string
Login string
Password string
}

func (n Netrc) BasicAuth(request *http.Request) (*http.Request, error) {
for _, l := range n {
if l.Machine != request.Host && l.Machine != "default" {
continue
}

request.SetBasicAuth(l.Login, l.Password)
break
}

return request, nil
}

func ParseNetrc(path string) (Netrc, error) {
b, err := ioutil.ReadFile(path)
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("unable to open %s\n%w", path, err)
}

var (
n Netrc
l NetrcLine
m = false
)

for _, line := range strings.Split(string(b), "\n") {
if m {
if line == "" {
m = false
}
continue
}

f := strings.Fields(line)
for i := 0; i < len(f); {
switch f[i] {
case "machine":
l = NetrcLine{Machine: f[i+1]}
i += 2
case "default":
l = NetrcLine{Machine: "default"}
i += 1
case "login":
l.Login = f[i+1]
i += 2
case "password":
l.Password = f[i+1]
i += 2
case "macdef":
m = true
i += 2
}

if l.Machine != "" && l.Login != "" && l.Password != "" {
n = append(n, l)

if l.Machine == "default" {
return n, nil
}

l = NetrcLine{}
}
}
}

return n, nil
}

func NetrcPath() (string, error) {
if s, ok := os.LookupEnv("NETRC"); ok {
return s, nil
}

u, err := user.Current()
if err != nil {
return "", fmt.Errorf("unable to determine user home directory\n%w", err)
}

return filepath.Join(u.HomeDir, ".netrc"), nil
}
218 changes: 218 additions & 0 deletions carton/netrc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package carton_test

import (
"io/ioutil"
"net/http"
"os"
"os/user"
"path/filepath"
"testing"

. "github.com/onsi/gomega"
"github.com/sclevine/spec"

"github.com/paketo-buildpacks/libpak/carton"
)

func testNetrc(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

path string
)

it.Before(func() {
var err error

f, err := ioutil.TempFile("", "netrc")
Expect(err).NotTo(HaveOccurred())
Expect(f.Close()).To(Succeed())
path = f.Name()
})

it.After(func() {
Expect(os.RemoveAll(path)).To(Succeed())
})

context("path", func() {
context("$NETRC", func() {
it.Before(func() {
Expect(os.Setenv("NETRC", "test-value")).To(Succeed())
})

it.After(func() {
Expect(os.Unsetenv("NETRC")).To(Succeed())
})

it("returns value from env var", func() {
Expect(carton.NetrcPath()).To(Equal("test-value"))
})
})

it("returns default", func() {
u, err := user.Current()
Expect(err).NotTo(HaveOccurred())

Expect(carton.NetrcPath()).To(Equal(filepath.Join(u.HomeDir, ".netrc")))
})
})

context("parse", func() {
it("parses one-liner", func() {
Expect(ioutil.WriteFile(path, []byte(`machine test-machine login test-login password test-password`), 0644)).To(Succeed())

Expect(carton.ParseNetrc(path)).To(Equal(carton.Netrc{
{
Machine: "test-machine",
Login: "test-login",
Password: "test-password",
},
}))
})

it("parses multi-liner", func() {
Expect(ioutil.WriteFile(path, []byte(`
machine test-machine
login test-login
password test-password
`), 0644)).To(Succeed())

Expect(carton.ParseNetrc(path)).To(Equal(carton.Netrc{
{
Machine: "test-machine",
Login: "test-login",
Password: "test-password",
},
}))
})

it("ignores macdef", func() {
Expect(ioutil.WriteFile(path, []byte(`
macdef uploadtest
cd /pub/tests
bin
put filename.tar.gz
quit
machine test-machine login test-login password test-password
`), 0644)).To(Succeed())

Expect(carton.ParseNetrc(path)).To(Equal(carton.Netrc{
{
Machine: "test-machine",
Login: "test-login",
Password: "test-password",
},
}))
})

it("ignores all after default", func() {
Expect(ioutil.WriteFile(path, []byte(`
machine test-machine-1 login test-login-1 password test-password-1
default
login test-login-2
password test-password-2
machine test-machine-3 login test-login-3 password test-password-3
`), 0644)).To(Succeed())

Expect(carton.ParseNetrc(path)).To(Equal(carton.Netrc{
{
Machine: "test-machine-1",
Login: "test-login-1",
Password: "test-password-1",
},
{
Machine: "default",
Login: "test-login-2",
Password: "test-password-2",
},
}))
})
})

context("basic auth", func() {
it("does not apply auth if no candidates", func() {
n := carton.Netrc{
{
Machine: "test-machine",
Login: "test-login",
Password: "test-password",
},
}

req, err := http.NewRequest("GET", "http://another-machine", nil)
Expect(err).NotTo(HaveOccurred())

req, err = n.BasicAuth(req)
Expect(err).NotTo(HaveOccurred())

_, _, ok := req.BasicAuth()
Expect(ok).To(BeFalse())
})

it("applies basic auth for match", func() {
n := carton.Netrc{
{
Machine: "test-machine",
Login: "test-login",
Password: "test-password",
},
}

req, err := http.NewRequest("GET", "http://test-machine", nil)
Expect(err).NotTo(HaveOccurred())

req, err = n.BasicAuth(req)
Expect(err).NotTo(HaveOccurred())

u, p, ok := req.BasicAuth()
Expect(ok).To(BeTrue())
Expect(u).To(Equal("test-login"))
Expect(p).To(Equal("test-password"))
})

it("applies basic auth for default", func() {
n := carton.Netrc{
{
Machine: "test-machine",
Login: "test-login",
Password: "test-password",
},
{
Machine: "default",
Login: "default-login",
Password: "default-password",
},
}

req, err := http.NewRequest("GET", "http://another-machine", nil)
Expect(err).NotTo(HaveOccurred())

req, err = n.BasicAuth(req)
Expect(err).NotTo(HaveOccurred())

u, p, ok := req.BasicAuth()
Expect(ok).To(BeTrue())
Expect(u).To(Equal("default-login"))
Expect(p).To(Equal("default-password"))
})
})
}
14 changes: 13 additions & 1 deletion carton/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,22 @@ func (p Package) Create(options ...Option) {
cache.DownloadPath = filepath.Join(p.Source, "dependencies")
}

np, err := NetrcPath()
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to determine netrc path\n%w", err))
return
}

n, err := ParseNetrc(np)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to read %s as netrc\n%w", np, err))
return
}

for _, dep := range metadata.Dependencies {
logger.Headerf("Caching %s", color.BlueString("%s %s", dep.Name, dep.Version))

f, err := cache.Artifact(dep)
f, err := cache.Artifact(dep, n.BasicAuth)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to download %s\n%w", dep.URI, err))
return
Expand Down
Loading

0 comments on commit 38155c5

Please sign in to comment.