pkg/netrc/netrc.go (101 lines of code) (raw):
// Copyright 2022 Google LLC All Rights Reserved.
//
// 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
//
// http://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 netrc provides functions to modify an netrc file.
package netrc
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/GoogleCloudPlatform/artifact-registry-go-tools/pkg/auth"
)
var arTokenConfigRegexp = regexp.MustCompile("machine (.*go.pkg.dev)\nlogin oauth2accesstoken\npassword (.*)")
func arTokenConfigPlaceholder(host string) string {
return fmt.Sprintf(`machine %s
login oauth2accesstoken
password <oauth2accesstoken>
`, host)
}
func arJsonKeyConfig(host, base64key string) string {
return fmt.Sprintf(`machine %s
login _json_key_base64
password %s
`, host, base64key)
}
func getNetrcFileName() string {
// return _netrc for Windows and .netrc for Unix
if runtime.GOOS == "windows" {
return "_netrc"
}
return ".netrc"
}
// Load loads the path and contents of the .netrc file into memory.
func Load() (string, string, error) {
netrcPath := os.Getenv("NETRC")
if netrcPath == "" {
h, err := os.UserHomeDir()
if err != nil {
return "", "", fmt.Errorf("cannot load .netrc file: %v", err)
}
netrcPath = h
}
netrcFileName := getNetrcFileName()
if !strings.HasSuffix(netrcPath, netrcFileName) {
netrcPath = filepath.Join(netrcPath, netrcFileName)
}
if _, err := os.Stat(filepath.Dir(netrcPath)); err != nil {
if os.IsNotExist(err) {
return "", "", fmt.Errorf("%s directory does not exist: %w", netrcFileName, err)
}
return "", "", fmt.Errorf("failed to load %s directory: %w", netrcFileName, err)
}
data, err := ioutil.ReadFile(netrcPath)
if os.IsNotExist(err) {
// The .netrc file does not exist; create a new one
return netrcPath, "", nil
}
if err != nil {
return "", "", fmt.Errorf("cannot load %s file: %v", netrcFileName, err)
}
return netrcPath, string(data), nil
}
// Save saves the .netrc file.
func Save(netrc, path string) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
defer f.Close()
if err != nil {
return fmt.Errorf("Save: %v", err)
}
if _, err := f.WriteString(netrc); err != nil {
return fmt.Errorf("Save: %v", err)
}
return nil
}
// Refresh updates the oauth tokens for all Artifact Registry Go endpoints.
func Refresh(netrc, token string) string {
return arTokenConfigRegexp.ReplaceAllString(netrc, "machine $1\nlogin oauth2accesstoken\npassword "+token)
}
// AddConfigs adds a config for every new location in locations. If jsonKeyPath
// is an empty string, the config with use an oauth token for login.
func AddConfigs(locations []string, netrc, hostPattern, jsonKeyPath string) (string, error) {
for _, l := range locations {
h := fmt.Sprintf(hostPattern, l)
match, err := regexp.MatchString("machine "+h+"\n", netrc)
if err != nil {
return "", fmt.Errorf("Internal: AddConfigs has error: %v", err)
}
if match {
log.Printf("Warning: machine %s is already in the .netrc file, skipping\n", h)
continue
}
var cfg string
if jsonKeyPath == "" {
cfg = arTokenConfigPlaceholder(h)
} else {
key, err := auth.EncodeJsonKey(jsonKeyPath)
if err != nil {
return "", fmt.Errorf("AddConfigs: %w", err)
}
cfg = arJsonKeyConfig(h, key)
}
if netrc != "" {
netrc = netrc + "\n"
}
netrc = netrc + cfg
}
return netrc, nil
}