diff --git a/Gopkg.lock b/Gopkg.lock index 78e60d66d39..7cad15e4717 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -283,6 +283,14 @@ revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" version = "v2.0.1" +[[projects]] + digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "NUT" + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" name = "github.com/pmezard/go-difflib" @@ -496,6 +504,7 @@ "github.com/libvirt/libvirt-go", "github.com/openshift/hive/contrib/pkg/awstagdeprovision", "github.com/pborman/uuid", + "github.com/pkg/errors", "github.com/shurcooL/vfsgen", "github.com/sirupsen/logrus", "github.com/stretchr/testify/assert", diff --git a/Gopkg.toml b/Gopkg.toml index 54baa1fb1a5..f2abc0fc238 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -52,3 +52,7 @@ ignored = ["github.com/openshift/installer/tests*"] [[constraint]] name = "k8s.io/client-go" version = "6.0.0" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.0" diff --git a/cmd/openshift-install/main.go b/cmd/openshift-install/main.go index c14d57095d3..8093ec46022 100644 --- a/cmd/openshift-install/main.go +++ b/cmd/openshift-install/main.go @@ -82,7 +82,7 @@ func main() { for _, asset := range targetAssets { st, err := assetStore.Fetch(asset) if err != nil { - logrus.Fatalf("Failed to generate asset: %v", err) + logrus.Fatalf("Failed to generate %s: %v", asset.Name(), err) } if err := st.PersistToFile(*dirFlag); err != nil { diff --git a/pkg/asset/asset.go b/pkg/asset/asset.go index ec6884912fb..c3f3fe02c0d 100644 --- a/pkg/asset/asset.go +++ b/pkg/asset/asset.go @@ -1,8 +1,9 @@ package asset import ( - "fmt" "path/filepath" + + "github.com/pkg/errors" ) // Asset used to install OpenShift. @@ -22,7 +23,7 @@ type Asset interface { func GetDataByFilename(a Asset, parents map[Asset]*State, filename string) ([]byte, error) { st, ok := parents[a] if !ok { - return nil, fmt.Errorf("failed to find %T in parents", a) + return nil, errors.Errorf("failed to find %T in parents", a) } for _, c := range st.Contents { @@ -30,5 +31,5 @@ func GetDataByFilename(a Asset, parents map[Asset]*State, filename string) ([]by return c.Data, nil } } - return nil, fmt.Errorf("failed to find data in %v with filename == %q", st, filename) + return nil, errors.Errorf("failed to find data in %v with filename == %q", st, filename) } diff --git a/pkg/asset/cluster/cluster.go b/pkg/asset/cluster/cluster.go index 348d2b8e6fc..2222db57cde 100644 --- a/pkg/asset/cluster/cluster.go +++ b/pkg/asset/cluster/cluster.go @@ -2,11 +2,11 @@ package cluster import ( "encoding/json" - "fmt" "io/ioutil" "os" "path/filepath" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/openshift/installer/data" @@ -45,23 +45,23 @@ func (c *Cluster) Dependencies() []asset.Asset { func (c *Cluster) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { state, ok := parents[c.tfvars] if !ok { - return nil, fmt.Errorf("failed to get terraform.tfvar state in the parent asset states") + return nil, errors.Errorf("failed to get terraform.tfvars from parent") } // Copy the terraform.tfvars to a temp directory where the terraform will be invoked within. tmpDir, err := ioutil.TempDir(os.TempDir(), "openshift-install-") if err != nil { - return nil, fmt.Errorf("failed to create temp dir: %v", err) + return nil, errors.Wrap(err, "failed to create temp dir for terraform execution") } defer os.RemoveAll(tmpDir) if err := ioutil.WriteFile(filepath.Join(tmpDir, state.Contents[0].Name), state.Contents[0].Data, 0600); err != nil { - return nil, fmt.Errorf("failed to write terraform.tfvars file: %v", err) + return nil, errors.Wrap(err, "failed to write terraform.tfvars file") } var tfvars config.Cluster if err := json.Unmarshal(state.Contents[0].Data, &tfvars); err != nil { - return nil, fmt.Errorf("failed to unmarshal terraform tfvars file: %v", err) + return nil, errors.Wrap(err, "failed to Unmarshal terraform.tfvars file") } platform := string(tfvars.Platform) @@ -78,12 +78,12 @@ func (c *Cluster) Generate(parents map[asset.Asset]*asset.State) (*asset.State, // This runs the terraform in a temp directory, the tfstate file will be returned // to the asset store to persist it on the disk. if err := terraform.Init(tmpDir); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to initialize terraform") } stateFile, err := terraform.Apply(tmpDir) if err != nil { - err = fmt.Errorf("terraform failed: %v", err) + err = errors.Wrap(err, "failed to run terraform") } data, err2 := ioutil.ReadFile(stateFile) diff --git a/pkg/asset/cluster/tfvars.go b/pkg/asset/cluster/tfvars.go index a0709896ae1..5baf6a5582e 100644 --- a/pkg/asset/cluster/tfvars.go +++ b/pkg/asset/cluster/tfvars.go @@ -1,11 +1,10 @@ package cluster import ( - "fmt" - "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types/config" + "github.com/pkg/errors" ) const ( @@ -39,7 +38,7 @@ func (t *TerraformVariables) Dependencies() []asset.Asset { func (t *TerraformVariables) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { installCfg, err := installconfig.GetInstallConfig(t.installConfig, parents) if err != nil { - return nil, fmt.Errorf("failed to get install config state in the parent asset states") + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } contents := map[asset.Asset][]string{} @@ -51,7 +50,7 @@ func (t *TerraformVariables) Generate(parents map[asset.Asset]*asset.State) (*as } { state, ok := parents[ign] if !ok { - return nil, fmt.Errorf("failed to get the ignition state for %v in the parent asset states", ign) + return nil, errors.Errorf("failed to get the ignition state for %v in the parent asset states", ign) } for _, content := range state.Contents { @@ -72,7 +71,7 @@ func (t *TerraformVariables) Generate(parents map[asset.Asset]*asset.State) (*as data, err := cluster.TFVars() if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get Tfvars") } return &asset.State{ diff --git a/pkg/asset/ignition/bootstrap/bootstrap.go b/pkg/asset/ignition/bootstrap/bootstrap.go index 51e6fc9bad7..1ce9f5c8718 100644 --- a/pkg/asset/ignition/bootstrap/bootstrap.go +++ b/pkg/asset/ignition/bootstrap/bootstrap.go @@ -10,6 +10,7 @@ import ( "github.com/coreos/ignition/config/util" igntypes "github.com/coreos/ignition/config/v2_2/types" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/openshift/installer/pkg/asset" @@ -133,12 +134,12 @@ func (a *bootstrap) Dependencies() []asset.Asset { func (a *bootstrap) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { installConfig, err := installconfig.GetInstallConfig(a.installConfig, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } templateData, err := a.getTemplateData(installConfig) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get bootstrap templates") } config := igntypes.Config{ @@ -166,7 +167,7 @@ func (a *bootstrap) Generate(dependencies map[asset.Asset]*asset.State) (*asset. data, err := json.Marshal(config) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Marshal Ignition config") } return &asset.State{ @@ -186,7 +187,7 @@ func (a *bootstrap) Name() string { func (a *bootstrap) getTemplateData(installConfig *types.InstallConfig) (*bootstrapTemplateData, error) { clusterDNSIP, err := installconfig.ClusterDNSIP(installConfig) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get ClusterDNSIP from InstallConfig") } etcdEndpoints := make([]string, installConfig.MasterCount()) for i := range etcdEndpoints { diff --git a/pkg/asset/ignition/machine/master.go b/pkg/asset/ignition/machine/master.go index b7e19dfbf4a..31c668d1080 100644 --- a/pkg/asset/ignition/machine/master.go +++ b/pkg/asset/ignition/machine/master.go @@ -3,6 +3,8 @@ package machine import ( "fmt" + "github.com/pkg/errors" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" @@ -39,7 +41,7 @@ func (a *master) Dependencies() []asset.Asset { func (a *master) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { installConfig, err := installconfig.GetInstallConfig(a.installConfig, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } state := &asset.State{ diff --git a/pkg/asset/ignition/machine/worker.go b/pkg/asset/ignition/machine/worker.go index 15b37c85385..2b92ffa6648 100644 --- a/pkg/asset/ignition/machine/worker.go +++ b/pkg/asset/ignition/machine/worker.go @@ -1,6 +1,8 @@ package machine import ( + "github.com/pkg/errors" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" @@ -37,7 +39,7 @@ func (a *worker) Dependencies() []asset.Asset { func (a *worker) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { installConfig, err := installconfig.GetInstallConfig(a.installConfig, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } return &asset.State{ diff --git a/pkg/asset/installconfig/installconfig.go b/pkg/asset/installconfig/installconfig.go index 79b7e769015..33a7833d86f 100644 --- a/pkg/asset/installconfig/installconfig.go +++ b/pkg/asset/installconfig/installconfig.go @@ -7,7 +7,7 @@ import ( "github.com/apparentlymart/go-cidr/cidr" "github.com/ghodss/yaml" - + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/openshift/installer/pkg/asset" @@ -131,7 +131,7 @@ func (a *installConfig) Generate(dependencies map[asset.Asset]*asset.State) (*as data, err := yaml.Marshal(installConfig) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Marshal InstallConfig") } return &asset.State{ @@ -155,13 +155,12 @@ func GetInstallConfig(installConfig asset.Asset, parents map[asset.Asset]*asset. st, ok := parents[installConfig] if !ok { - return nil, fmt.Errorf("failed to find %T in parents", installConfig) + return nil, errors.Errorf("%T does not exist in parents", installConfig) } if err := yaml.Unmarshal(st.Contents[0].Data, &cfg); err != nil { - return nil, fmt.Errorf("failed to unmarshal the installconfig: %v", err) + return nil, errors.Wrap(err, "failed to Unmarshal the installconfig") } - return &cfg, nil } diff --git a/pkg/asset/installconfig/platform.go b/pkg/asset/installconfig/platform.go index fcdbb68c4cc..c4bbebb81a2 100644 --- a/pkg/asset/installconfig/platform.go +++ b/pkg/asset/installconfig/platform.go @@ -8,6 +8,7 @@ import ( "sort" "strings" + "github.com/pkg/errors" survey "gopkg.in/AlecAivazis/survey.v1" "github.com/openshift/installer/pkg/asset" @@ -100,6 +101,7 @@ func (a *Platform) Name() string { func (a *Platform) queryUserForPlatform() (string, error) { sort.Strings(validPlatforms) prompt := asset.UserProvided{ + AssetName: "Platform", Question: &survey.Question{ Prompt: &survey.Select{ Message: "Platform", @@ -109,7 +111,7 @@ func (a *Platform) queryUserForPlatform() (string, error) { choice := ans.(string) i := sort.SearchStrings(validPlatforms, choice) if i == len(validPlatforms) || validPlatforms[i] != choice { - return fmt.Errorf("invalid platform %q", choice) + return errors.Errorf("invalid platform %q", choice) } return nil }), @@ -142,6 +144,7 @@ func (a *Platform) awsPlatform() (*asset.State, error) { sort.Strings(longRegions) sort.Strings(shortRegions) prompt := asset.UserProvided{ + AssetName: "AWS Region", Question: &survey.Question{ Prompt: &survey.Select{ Message: "Region", @@ -153,7 +156,7 @@ func (a *Platform) awsPlatform() (*asset.State, error) { choice := regionTransform(ans).(string) i := sort.SearchStrings(shortRegions, choice) if i == len(shortRegions) || shortRegions[i] != choice { - return fmt.Errorf("invalid region %q", choice) + return errors.Errorf("invalid region %q", choice) } return nil }), @@ -169,13 +172,13 @@ func (a *Platform) awsPlatform() (*asset.State, error) { if value, ok := os.LookupEnv("_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS"); ok { if err := json.Unmarshal([]byte(value), &platform.UserTags); err != nil { - return nil, fmt.Errorf("_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS contains invalid JSON: %s (%v)", value, err) + return nil, errors.Wrapf(err, "_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS contains invalid JSON: %s", value) } } data, err := json.Marshal(platform) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to Marshal %s platform", AWSPlatformType) } return &asset.State{ @@ -197,6 +200,7 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { NetworkCIDRBlock: defaultVPCCIDR, } prompt := asset.UserProvided{ + AssetName: "OpenStack Region", Question: &survey.Question{ Prompt: &survey.Select{ Message: "Region", @@ -217,6 +221,7 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { } platform.Region = string(region.Contents[0].Data) prompt2 := asset.UserProvided{ + AssetName: "OpenStack Image", Question: &survey.Question{ Prompt: &survey.Select{ Message: "Image", @@ -237,6 +242,7 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { } platform.BaseImage = string(image.Contents[0].Data) prompt3 := asset.UserProvided{ + AssetName: "OpenStack Cloud", Question: &survey.Question{ Prompt: &survey.Select{ Message: "Cloud", @@ -256,6 +262,7 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { } platform.Cloud = string(cloud.Contents[0].Data) prompt4 := asset.UserProvided{ + AssetName: "OpenStack External Network", Question: &survey.Question{ Prompt: &survey.Select{ Message: "ExternalNetwork", @@ -277,7 +284,7 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { data, err := json.Marshal(platform) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to Marshal %s platform", OpenStackPlatformType) } return &asset.State{ @@ -307,6 +314,7 @@ func (a *Platform) libvirtPlatform() (*asset.State, error) { } uriPrompt := asset.UserProvided{ + AssetName: "Libvirt Connection URI", Question: &survey.Question{ Prompt: &survey.Input{ Message: "Libvirt Connection URI", @@ -323,6 +331,7 @@ func (a *Platform) libvirtPlatform() (*asset.State, error) { } imagePrompt := asset.UserProvided{ + AssetName: "Libvirt Image", Question: &survey.Question{ Prompt: &survey.Input{ Message: "Image", @@ -342,7 +351,7 @@ func (a *Platform) libvirtPlatform() (*asset.State, error) { data, err := json.Marshal(platform) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to Marshal %s platform", LibvirtPlatformType) } return &asset.State{ diff --git a/pkg/asset/installconfig/ssh.go b/pkg/asset/installconfig/ssh.go index 73ef5d24ab8..13c2a31cd2b 100644 --- a/pkg/asset/installconfig/ssh.go +++ b/pkg/asset/installconfig/ssh.go @@ -1,7 +1,6 @@ package installconfig import ( - "errors" "fmt" "io/ioutil" "os" @@ -10,6 +9,7 @@ import ( "sort" "strings" + "github.com/pkg/errors" survey "gopkg.in/AlecAivazis/survey.v1" "github.com/openshift/installer/pkg/asset" @@ -45,7 +45,7 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat if value, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY"); ok { if value != "" { if err := validateOpenSSHPublicKey(value); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to validate public key") } } return &asset.State{ @@ -59,7 +59,7 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat if path, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY_PATH"); ok { key, err := readSSHKey(path) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read public key file") } pubKeys[path] = key } else { @@ -68,7 +68,7 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat if home != "" { paths, err := filepath.Glob(filepath.Join(home, ".ssh", "*.pub")) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to glob for public key files") } for _, path := range paths { key, err := readSSHKey(path) @@ -111,7 +111,7 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat return nil }) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed UserInput for SSH public key") } return &asset.State{ diff --git a/pkg/asset/kubeconfig/kubeconfig.go b/pkg/asset/kubeconfig/kubeconfig.go index 4e44a25ee26..be645290861 100644 --- a/pkg/asset/kubeconfig/kubeconfig.go +++ b/pkg/asset/kubeconfig/kubeconfig.go @@ -5,10 +5,12 @@ import ( "path/filepath" "github.com/ghodss/yaml" + "github.com/pkg/errors" + clientcmd "k8s.io/client-go/tools/clientcmd/api/v1" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" - clientcmd "k8s.io/client-go/tools/clientcmd/api/v1" ) const ( @@ -40,11 +42,9 @@ func (k *Kubeconfig) Dependencies() []asset.Asset { // Generate generates the kubeconfig. func (k *Kubeconfig) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { - var err error - caCertData, err := asset.GetDataByFilename(k.rootCA, parents, tls.RootCACertName) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get RootCA from parents") } var keyFilename, certFilename, kubeconfigSuffix string @@ -57,15 +57,15 @@ func (k *Kubeconfig) Generate(parents map[asset.Asset]*asset.State) (*asset.Stat } clientKeyData, err := asset.GetDataByFilename(k.certKey, parents, keyFilename) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get client certificate from parents") } clientCertData, err := asset.GetDataByFilename(k.certKey, parents, certFilename) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get client key from parents") } installConfig, err := installconfig.GetInstallConfig(k.installConfig, parents) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } kubeconfig := clientcmd.Config{ @@ -101,7 +101,7 @@ func (k *Kubeconfig) Generate(parents map[asset.Asset]*asset.State) (*asset.Stat data, err := yaml.Marshal(kubeconfig) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Marshal kubeconfig") } return &asset.State{ diff --git a/pkg/asset/kubeconfig/kubeconfig_test.go b/pkg/asset/kubeconfig/kubeconfig_test.go index e2b3afe9063..85bff2dddf9 100644 --- a/pkg/asset/kubeconfig/kubeconfig_test.go +++ b/pkg/asset/kubeconfig/kubeconfig_test.go @@ -153,7 +153,7 @@ users: adminCertKey: adminCertState, installConfig: installConfigState, }, - errString: "failed to find kubeconfig.fakeAsset in parents", + errString: "failed to get RootCA from parents: failed to find kubeconfig.fakeAsset in parents", expectedData: nil, }, { @@ -165,7 +165,7 @@ users: kubeletCertKey: kubeletCertState, installConfig: installConfigState, }, - errString: "failed to find kubeconfig.fakeAsset in parents", + errString: "failed to get client certificate from parents: failed to find kubeconfig.fakeAsset in parents", expectedData: nil, }, { @@ -177,7 +177,7 @@ users: adminCertKey: adminCertState, installConfig: installConfigState, }, - errString: "failed to find kubeconfig.fakeAsset in parents", + errString: "failed to get client certificate from parents: failed to find kubeconfig.fakeAsset in parents", expectedData: nil, }, { @@ -188,7 +188,7 @@ users: rootCA: rootCAState, adminCertKey: adminCertState, }, - errString: "failed to find kubeconfig.fakeAsset in parents", + errString: "failed to get InstallConfig from parents: kubeconfig.fakeAsset does not exist in parents", expectedData: nil, }, } diff --git a/pkg/asset/manifests/kube-addon-operator.go b/pkg/asset/manifests/kube-addon-operator.go index 03282b3e645..684d421f209 100644 --- a/pkg/asset/manifests/kube-addon-operator.go +++ b/pkg/asset/manifests/kube-addon-operator.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ghodss/yaml" + "github.com/pkg/errors" kubeaddon "github.com/coreos/tectonic-config/config/kube-addon" "github.com/openshift/installer/pkg/asset" @@ -38,14 +39,14 @@ func (kao *kubeAddonOperator) Dependencies() []asset.Asset { func (kao *kubeAddonOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { ic, err := installconfig.GetInstallConfig(kao.installConfigAsset, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } kao.installConfig = ic // installconfig is ready, we can create the addon config from it now addonConfig, err := kao.addonConfig() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", kao.Name()) } state := &asset.State{ diff --git a/pkg/asset/manifests/kube-core-operator.go b/pkg/asset/manifests/kube-core-operator.go index ccaff3983b0..006cf38f32a 100644 --- a/pkg/asset/manifests/kube-core-operator.go +++ b/pkg/asset/manifests/kube-core-operator.go @@ -4,14 +4,15 @@ import ( "fmt" "strings" - "github.com/ghodss/yaml" - "github.com/apparentlymart/go-cidr/cidr" kubecore "github.com/coreos/tectonic-config/config/kube-core" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -46,32 +47,28 @@ func (kco *kubeCoreOperator) Dependencies() []asset.Asset { func (kco *kubeCoreOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { ic, err := installconfig.GetInstallConfig(kco.installConfigAsset, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } kco.installConfig = ic // installconfig is ready, we can create the core config from it now coreConfig, err := kco.coreConfig() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", kco.Name()) } - data, err := yaml.Marshal(coreConfig) - if err != nil { - return nil, fmt.Errorf("failed to marshal core config: %v", err) - } state := &asset.State{ Contents: []asset.Content{ { Name: "kco-config.yaml", - Data: data, + Data: coreConfig, }, }, } return state, nil } -func (kco *kubeCoreOperator) coreConfig() (*kubecore.OperatorConfig, error) { +func (kco *kubeCoreOperator) coreConfig() ([]byte, error) { coreConfig := kubecore.OperatorConfig{ TypeMeta: metav1.TypeMeta{ APIVersion: kubecore.APIVersion, @@ -99,8 +96,7 @@ func (kco *kubeCoreOperator) coreConfig() (*kubecore.OperatorConfig, error) { coreConfig.NetworkConfig.ServiceCIDR = kco.installConfig.Networking.ServiceCIDR.String() coreConfig.NetworkConfig.AdvertiseAddress = networkConfigAdvertiseAddress coreConfig.NetworkConfig.EtcdServers = strings.Join(kco.getEtcdServersURLs(), ",") - - return &coreConfig, nil + return yaml.Marshal(coreConfig) } func (kco *kubeCoreOperator) getAPIServerURL() string { diff --git a/pkg/asset/manifests/machine-api-operator.go b/pkg/asset/manifests/machine-api-operator.go index daf2a080eab..f839caef9ca 100644 --- a/pkg/asset/manifests/machine-api-operator.go +++ b/pkg/asset/manifests/machine-api-operator.go @@ -2,12 +2,12 @@ package manifests import ( "context" - "fmt" "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/rhcos" "github.com/openshift/installer/pkg/types" + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -80,14 +80,14 @@ func (mao *machineAPIOperator) Dependencies() []asset.Asset { func (mao *machineAPIOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { ic, err := installconfig.GetInstallConfig(mao.installConfigAsset, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } mao.installConfig = ic // installconfig is ready, we can create the mao config from it now maoConfig, err := mao.maoConfig(dependencies) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", mao.Name()) } state := &asset.State{ @@ -120,7 +120,7 @@ func (mao *machineAPIOperator) maoConfig(dependencies map[asset.Asset]*asset.Sta ami, err := rhcos.AMI(context.TODO(), DefaultChannel, mao.installConfig.Platform.AWS.Region) if err != nil { - return "", fmt.Errorf("failed to lookup RHCOS AMI: %v", err) + return "", errors.Wrapf(err, "failed to get AMI for %s config", mao.Name()) } cfg.AWS = &awsConfig{ @@ -147,7 +147,7 @@ func (mao *machineAPIOperator) maoConfig(dependencies map[asset.Asset]*asset.Sta Replicas: int(*mao.installConfig.Machines[1].Replicas), } } else { - return "", fmt.Errorf("unknown provider for machine-api-operator") + return "", errors.Errorf("unknown provider for machine-api-operator") } return marshalYAML(cfg) diff --git a/pkg/asset/manifests/network-operator.go b/pkg/asset/manifests/network-operator.go index ee7cd0e8f24..e0297cf99be 100644 --- a/pkg/asset/manifests/network-operator.go +++ b/pkg/asset/manifests/network-operator.go @@ -2,6 +2,7 @@ package manifests import ( "github.com/ghodss/yaml" + "github.com/pkg/errors" "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" @@ -40,19 +41,19 @@ func (no *networkOperator) Dependencies() []asset.Asset { func (no *networkOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { ic, err := installconfig.GetInstallConfig(no.installConfigAsset, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } no.installConfig = ic // installconfig is ready, we can create the core config from it now netConfig, err := no.netConfig() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", no.Name()) } netManifest, err := no.manifest() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create %s manifests from InstallConfig", no.Name()) } state := &asset.State{ Contents: []asset.Content{ diff --git a/pkg/asset/manifests/operators.go b/pkg/asset/manifests/operators.go index 138552efc21..5064658e15e 100644 --- a/pkg/asset/manifests/operators.go +++ b/pkg/asset/manifests/operators.go @@ -7,6 +7,8 @@ import ( "path/filepath" "text/template" + "github.com/pkg/errors" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/manifests/content/bootkube" @@ -95,7 +97,7 @@ func (m *manifests) Generate(dependencies map[asset.Asset]*asset.State) (*asset. "mao-config": string(mao.Data), }) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create kube-system/cluster-config-v1 configmap") } // addon goes to openshift system @@ -103,7 +105,7 @@ func (m *manifests) Generate(dependencies map[asset.Asset]*asset.State) (*asset. "addon-config": string(addon.Data), }) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create tectonic-system/cluster-config-v1 configmap") } state := &asset.State{ diff --git a/pkg/asset/manifests/tectonic.go b/pkg/asset/manifests/tectonic.go index bfe5778a3b4..c8d4e5bc9d4 100644 --- a/pkg/asset/manifests/tectonic.go +++ b/pkg/asset/manifests/tectonic.go @@ -5,6 +5,8 @@ import ( "encoding/base64" "path/filepath" + "github.com/pkg/errors" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" content "github.com/openshift/installer/pkg/asset/manifests/content/tectonic" @@ -38,7 +40,7 @@ func (t *tectonic) Dependencies() []asset.Asset { func (t *tectonic) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { ic, err := installconfig.GetInstallConfig(t.installConfig, dependencies) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } ingressContents := dependencies[t.ingressCertKey].Contents templateData := &tectonicTemplateData{ diff --git a/pkg/asset/metadata/metadata.go b/pkg/asset/metadata/metadata.go index 14cbaeb54be..115d63c24ab 100644 --- a/pkg/asset/metadata/metadata.go +++ b/pkg/asset/metadata/metadata.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" + "github.com/pkg/errors" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" @@ -37,7 +39,7 @@ func (m *Metadata) Dependencies() []asset.Asset { func (m *Metadata) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { installCfg, err := installconfig.GetInstallConfig(m.installConfig, parents) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } cm := &types.ClusterMetadata{ @@ -68,7 +70,7 @@ func (m *Metadata) Generate(parents map[asset.Asset]*asset.State) (*asset.State, data, err := json.Marshal(cm) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Marshal ClusterMetadata") } return &asset.State{ Contents: []asset.Content{ diff --git a/pkg/asset/state.go b/pkg/asset/state.go index f94c9cb4762..bd92151627c 100644 --- a/pkg/asset/state.go +++ b/pkg/asset/state.go @@ -4,6 +4,8 @@ import ( "io/ioutil" "os" "path/filepath" + + "github.com/pkg/errors" ) // State is the state of an Asset. @@ -20,16 +22,20 @@ type Content struct { // PersistToFile persists the data in the State to files. Each Content entry that // has a non-empty Name will be persisted to a file with that name. func (s *State) PersistToFile(directory string) error { + if s == nil { + return nil + } + for _, c := range s.Contents { if c.Name == "" { continue } path := filepath.Join(directory, c.Name) if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return err + return errors.Wrap(err, "failed to create dir") } if err := ioutil.WriteFile(path, c.Data, 0644); err != nil { - return err + return errors.Wrap(err, "failed to write file") } } return nil diff --git a/pkg/asset/store.go b/pkg/asset/store.go index 0137ed1f520..eaf159306e2 100644 --- a/pkg/asset/store.go +++ b/pkg/asset/store.go @@ -1,8 +1,7 @@ package asset import ( - "fmt" - + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -40,7 +39,7 @@ func (s *StoreImpl) fetch(asset Asset, indent string) (*State, error) { for _, d := range dependencies { ds, err := s.fetch(d, indent+" ") if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to fetch dependency for %s", asset.Name()) } dependenciesStates[d] = ds } @@ -48,7 +47,7 @@ func (s *StoreImpl) fetch(asset Asset, indent string) (*State, error) { logrus.Debugf("%sGenerating %s...", indent, asset.Name()) state, err := asset.Generate(dependenciesStates) if err != nil { - return nil, fmt.Errorf("failed to generate asset %q: %v", asset.Name(), err) + return nil, errors.Wrapf(err, "failed to generate asset %s", asset.Name()) } if s.assets == nil { s.assets = make(map[Asset]*State) diff --git a/pkg/asset/tls/certkey.go b/pkg/asset/tls/certkey.go index bd83a77acf3..58e0ff14124 100644 --- a/pkg/asset/tls/certkey.go +++ b/pkg/asset/tls/certkey.go @@ -9,9 +9,10 @@ import ( "path/filepath" "time" - "github.com/ghodss/yaml" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" + "github.com/pkg/errors" ) const ( @@ -74,33 +75,27 @@ func (c *CertKey) Generate(parents map[asset.Asset]*asset.State) (*asset.State, } if c.GenSubject != nil || c.GenDNSNames != nil || c.GenIPAddresses != nil { - state, ok := parents[c.installConfig] - if !ok { - return nil, fmt.Errorf("failed to get install config state in the parent asset states") + installConfig, err := installconfig.GetInstallConfig(c.installConfig, parents) + if err != nil { + return nil, errors.Wrap(err, "failed to get InstallConfig from parents") } - var installConfig types.InstallConfig - if err := yaml.Unmarshal(state.Contents[0].Data, &installConfig); err != nil { - return nil, fmt.Errorf("failed to unmarshal install config: %v", err) - } - - var err error if c.GenSubject != nil { - cfg.Subject, err = c.GenSubject(&installConfig) + cfg.Subject, err = c.GenSubject(installConfig) if err != nil { - return nil, fmt.Errorf("failed to generate Subject: %v", err) + return nil, errors.Wrap(err, "failed to generate Subject") } } if c.GenDNSNames != nil { - cfg.DNSNames, err = c.GenDNSNames(&installConfig) + cfg.DNSNames, err = c.GenDNSNames(installConfig) if err != nil { - return nil, fmt.Errorf("failed to generate DNSNames: %v", err) + return nil, errors.Wrap(err, "failed to generate DNSNames") } } if c.GenIPAddresses != nil { - cfg.IPAddresses, err = c.GenIPAddresses(&installConfig) + cfg.IPAddresses, err = c.GenIPAddresses(installConfig) if err != nil { - return nil, fmt.Errorf("failed to generate IPAddresses: %v", err) + return nil, errors.Wrap(err, "failed to generate IPAddresses") } } } @@ -111,17 +106,17 @@ func (c *CertKey) Generate(parents map[asset.Asset]*asset.State) (*asset.State, state, ok := parents[c.ParentCA] if !ok { - return nil, fmt.Errorf("failed to get parent CA %v in the parent asset states", c.ParentCA) + return nil, errors.Errorf("failed to get parent CA %v in the parent asset states", c.ParentCA) } caKey, caCert, err := parseCAFromAssetState(state) if err != nil { - return nil, fmt.Errorf("failed to parse CA from asset: %v", err) + return nil, errors.Wrap(err, "failed to parse CA from asset") } key, crt, err = GenerateCert(caKey, caCert, cfg) if err != nil { - return nil, fmt.Errorf("failed to generate cert/key pair: %v", err) + return nil, errors.Wrap(err, "failed to generate cert/key pair") } keyData := []byte(PrivateKeyToPem(key)) @@ -156,7 +151,7 @@ func parseCAFromAssetState(ca *asset.State) (*rsa.PrivateKey, *x509.Certificate, var err error if len(ca.Contents) != 2 { - return nil, nil, fmt.Errorf("expect key and cert in the contents of CA, got: %v", ca) + return nil, nil, errors.Errorf("expected key and cert in the contents of CA, got: %v", ca) } for _, c := range ca.Contents { @@ -164,15 +159,15 @@ func parseCAFromAssetState(ca *asset.State) (*rsa.PrivateKey, *x509.Certificate, case ".key": key, err = PemToPrivateKey(c.Data) if err != nil { - return nil, nil, fmt.Errorf("failed to parse rsa private key: %v", err) + return nil, nil, errors.Wrap(err, "failed to parse rsa private key") } case ".crt": cert, err = PemToCertificate(c.Data) if err != nil { - return nil, nil, fmt.Errorf("failed to parse x509 certificate: %v", err) + return nil, nil, errors.Wrap(err, "failed to parse x509 certificate") } default: - return nil, nil, fmt.Errorf("unexpected content name: %v", c.Name) + return nil, nil, errors.Errorf("unexpected content name: %v", c.Name) } } diff --git a/pkg/asset/tls/certkey_test.go b/pkg/asset/tls/certkey_test.go index 68d26714dfd..b68c59c8deb 100644 --- a/pkg/asset/tls/certkey_test.go +++ b/pkg/asset/tls/certkey_test.go @@ -118,7 +118,7 @@ func TestCertKeyGenerate(t *testing.T) { GenDNSNames: testGenDNSNames, GenIPAddresses: testGenIPAddresses, }, - errString: "failed to get install config state in the parent asset states", + errString: "failed to get InstallConfig from parents: tls.fakeInstallConfig does not exist in parents", parents: nil, }, } diff --git a/pkg/asset/tls/keypair.go b/pkg/asset/tls/keypair.go index 6f485df84ae..40ea9351ef0 100644 --- a/pkg/asset/tls/keypair.go +++ b/pkg/asset/tls/keypair.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/openshift/installer/pkg/asset" + "github.com/pkg/errors" ) // KeyPair implements the Asset interface and @@ -24,12 +25,12 @@ func (k *KeyPair) Dependencies() []asset.Asset { func (k *KeyPair) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { key, err := PrivateKey() if err != nil { - return nil, fmt.Errorf("failed to generate private key: %v", err) + return nil, errors.Wrap(err, "failed to generate private key") } pubkeyData, err := PublicKeyToPem(&key.PublicKey) if err != nil { - return nil, fmt.Errorf("failed to get public key data: %v", err) + return nil, errors.Wrap(err, "failed to get public key data from private key") } return &asset.State{ diff --git a/pkg/asset/tls/root.go b/pkg/asset/tls/root.go index 67df4c5540e..29696745f42 100644 --- a/pkg/asset/tls/root.go +++ b/pkg/asset/tls/root.go @@ -3,9 +3,9 @@ package tls import ( "crypto/x509" "crypto/x509/pkix" - "fmt" "github.com/openshift/installer/pkg/asset" + "github.com/pkg/errors" ) // RootCA contains the private key and the cert that's @@ -30,7 +30,7 @@ func (c *RootCA) Generate(parents map[asset.Asset]*asset.State) (*asset.State, e key, crt, err := GenerateRootCertKey(cfg) if err != nil { - return nil, fmt.Errorf("failed to generate RootCA %v", err) + return nil, errors.Wrap(err, "failed to generate RootCA") } return &asset.State{ diff --git a/pkg/asset/tls/tls.go b/pkg/asset/tls/tls.go index 7990a6545d3..75957f38559 100644 --- a/pkg/asset/tls/tls.go +++ b/pkg/asset/tls/tls.go @@ -10,12 +10,12 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" - "errors" - "fmt" "math" "math/big" "net" "time" + + "github.com/pkg/errors" ) const ( @@ -50,7 +50,7 @@ type rsaPublicKey struct { func PrivateKey() (*rsa.PrivateKey, error) { rsaKey, err := rsa.GenerateKey(rand.Reader, keySize) if err != nil { - return nil, fmt.Errorf("error generating RSA private key: %v", err) + return nil, errors.Wrap(err, "error generating RSA private key") } return rsaKey, nil @@ -71,16 +71,16 @@ func SelfSignedCACert(cfg *CertCfg, key *rsa.PrivateKey) (*x509.Certificate, err } // verifies that the CN and/or OU for the cert is set if len(cfg.Subject.CommonName) == 0 || len(cfg.Subject.OrganizationalUnit) == 0 { - return nil, fmt.Errorf("certification's subject is not set, or invalid") + return nil, errors.Errorf("certification's subject is not set, or invalid") } pub := key.Public() cert.SubjectKeyId, err = generateSubjectKeyID(pub) if err != nil { - return nil, fmt.Errorf("failed to set subject key identifier: %v", err) + return nil, errors.Wrap(err, "failed to set subject key identifier") } certBytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, key.Public(), key) if err != nil { - return nil, fmt.Errorf("error creating certificate: %v", err) + return nil, errors.Wrap(err, "failed to create certificate") } return x509.ParseCertificate(certBytes) } @@ -114,11 +114,11 @@ func SignedCertificate( pub := caCert.PublicKey.(*rsa.PublicKey) certTmpl.SubjectKeyId, err = generateSubjectKeyID(pub) if err != nil { - return nil, fmt.Errorf("failed to set subject key identifier: %v", err) + return nil, errors.Wrap(err, "failed to set subject key identifier") } certBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey) if err != nil { - return nil, fmt.Errorf("error creating signed certificate: %v", err) + return nil, errors.Wrap(err, "failed to create x509 certificate") } return x509.ParseCertificate(certBytes) } @@ -132,7 +132,7 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { case *rsa.PublicKey: publicKeyBytes, err = asn1.Marshal(rsaPublicKey{N: pub.N, E: pub.E}) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Marshal ans1 public key") } case *ecdsa.PublicKey: publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y) @@ -154,24 +154,24 @@ func GenerateCert(caKey *rsa.PrivateKey, // create a private key key, err := PrivateKey() if err != nil { - return nil, nil, fmt.Errorf("failed to generate private key: %v", err) + return nil, nil, errors.Wrap(err, "failed to generate private key") } // create a CSR csrTmpl := x509.CertificateRequest{Subject: cfg.Subject, DNSNames: cfg.DNSNames, IPAddresses: cfg.IPAddresses} csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTmpl, key) if err != nil { - return nil, nil, fmt.Errorf("error creating certificate request: %v", err) + return nil, nil, errors.Wrap(err, "failed to create certificate request") } csr, err := x509.ParseCertificateRequest(csrBytes) if err != nil { - return nil, nil, fmt.Errorf("error parsing certificate request: %v", err) + return nil, nil, errors.Wrap(err, "error parsing x509 certificate request") } // create a cert cert, err := GenerateSignedCert(cfg, csr, key, caKey, caCert) if err != nil { - return nil, nil, fmt.Errorf("failed to create a certificate: %v", err) + return nil, nil, errors.Wrap(err, "failed to create a signed certificate") } return key, cert, nil } @@ -180,7 +180,7 @@ func GenerateCert(caKey *rsa.PrivateKey, func GenerateRootCA(key *rsa.PrivateKey, cfg *CertCfg) (*x509.Certificate, error) { cert, err := SelfSignedCACert(cfg, key) if err != nil { - return nil, fmt.Errorf("error generating self signed certificate: %v", err) + return nil, errors.Wrap(err, "failed to generate self signed certificate") } return cert, nil } @@ -193,7 +193,7 @@ func GenerateSignedCert(cfg *CertCfg, caCert *x509.Certificate) (*x509.Certificate, error) { cert, err := SignedCertificate(cfg, csr, key, caCert, caKey) if err != nil { - return nil, fmt.Errorf("error signing certificate: %v", err) + return nil, errors.Wrap(err, "failed to create a signed certificate") } return cert, nil } @@ -202,13 +202,12 @@ func GenerateSignedCert(cfg *CertCfg, func GenerateRootCertKey(cfg *CertCfg) (*rsa.PrivateKey, *x509.Certificate, error) { key, err := PrivateKey() if err != nil { - return nil, nil, fmt.Errorf("failed to generate private key: %v", err) + return nil, nil, errors.Wrap(err, "failed to generate private key") } crt, err := GenerateRootCA(key, cfg) if err != nil { - return nil, nil, fmt.Errorf("failed to create a certificate: %v", err) + return nil, nil, errors.Wrap(err, "failed to create root CA certificate") } - return key, crt, nil } diff --git a/pkg/asset/tls/utils.go b/pkg/asset/tls/utils.go index e972476d12e..c0414d8e90a 100644 --- a/pkg/asset/tls/utils.go +++ b/pkg/asset/tls/utils.go @@ -4,6 +4,8 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" + + "github.com/pkg/errors" ) // PrivateKeyToPem converts an rsa.PrivateKey object to pem string @@ -44,7 +46,7 @@ func CSRToPem(cert *x509.CertificateRequest) string { func PublicKeyToPem(key *rsa.PublicKey) (string, error) { keyInBytes, err := x509.MarshalPKIXPublicKey(key) if err != nil { - return "", err + return "", errors.Wrap(err, "failed to MarshalPKIXPublicKey") } keyinPem := pem.EncodeToMemory( &pem.Block{ diff --git a/pkg/asset/userprovided.go b/pkg/asset/userprovided.go index 611dba2df03..c6093e2ffb5 100644 --- a/pkg/asset/userprovided.go +++ b/pkg/asset/userprovided.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" + "github.com/pkg/errors" survey "gopkg.in/AlecAivazis/survey.v1" ) @@ -23,7 +24,13 @@ func (a *UserProvided) Dependencies() []Asset { } // Generate queries for input from the user. -func (a *UserProvided) Generate(map[Asset]*State) (*State, error) { +func (a *UserProvided) Generate(map[Asset]*State) (state *State, err error) { + defer func() { + if err != nil { + err = errors.Wrapf(err, "failed to acquire user-provided input %s", a.AssetName) + } + }() + var response string if value, ok := os.LookupEnv(a.EnvVarName); ok { @@ -31,18 +38,18 @@ func (a *UserProvided) Generate(map[Asset]*State) (*State, error) { } else if path, ok := os.LookupEnv(a.PathEnvVarName); ok { value, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to read file from %s", a.PathEnvVarName) } response = string(value) } if response == "" { if err := survey.Ask([]*survey.Question{a.Question}, &response); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Ask") } } else if a.Question.Validate != nil { if err := a.Question.Validate(response); err != nil { - return nil, err + return nil, errors.Wrap(err, "validation failed") } } diff --git a/pkg/destroy/destroyer.go b/pkg/destroy/destroyer.go index 20e0493ecec..00e82badbf4 100644 --- a/pkg/destroy/destroyer.go +++ b/pkg/destroy/destroyer.go @@ -2,10 +2,10 @@ package destroy import ( "encoding/json" - "fmt" "io/ioutil" "path/filepath" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/openshift/installer/pkg/asset/metadata" @@ -29,22 +29,22 @@ func New(logger logrus.FieldLogger, rootDir string) (Destroyer, error) { path := filepath.Join(rootDir, metadata.MetadataFilename) raw, err := ioutil.ReadFile(filepath.Join(rootDir, metadata.MetadataFilename)) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to read %s file", metadata.MetadataFilename) } var cmetadata *types.ClusterMetadata if err := json.Unmarshal(raw, &cmetadata); err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to Unmarshal data from %s file to types.ClusterMetadata", metadata.MetadataFilename) } platform := cmetadata.Platform() if platform == "" { - return nil, fmt.Errorf("no platform configured in %q", path) + return nil, errors.Errorf("no platform configured in %q", path) } creator, ok := Registry[platform] if !ok { - return nil, fmt.Errorf("no destroyers registered for %q", platform) + return nil, errors.Errorf("no destroyers registered for %q", platform) } return creator(logger, cmetadata) } diff --git a/pkg/destroy/libvirt/libvirt_prefix_deprovision.go b/pkg/destroy/libvirt/libvirt_prefix_deprovision.go index 3891fbc7889..3ebf6b1e692 100644 --- a/pkg/destroy/libvirt/libvirt_prefix_deprovision.go +++ b/pkg/destroy/libvirt/libvirt_prefix_deprovision.go @@ -8,10 +8,12 @@ import ( "time" libvirt "github.com/libvirt/libvirt-go" - "github.com/openshift/installer/pkg/destroy" - "github.com/openshift/installer/pkg/types" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/wait" + + "github.com/openshift/installer/pkg/destroy" + "github.com/openshift/installer/pkg/types" ) // filterFunc allows filtering based on names. @@ -61,7 +63,7 @@ func (o *ClusterUninstaller) Run() error { conn, err := libvirt.NewConnect(o.LibvirtURI) if err != nil { - return err + return errors.Wrap(err, "failed to connect to Libvirt daemon") } // launch goroutines diff --git a/pkg/ipnet/ipnet.go b/pkg/ipnet/ipnet.go index 59c20d976a6..eff16ee0c8f 100644 --- a/pkg/ipnet/ipnet.go +++ b/pkg/ipnet/ipnet.go @@ -5,6 +5,8 @@ import ( "encoding/json" "net" "reflect" + + "github.com/pkg/errors" ) var nullString = "null" @@ -45,12 +47,12 @@ func (ipnet *IPNet) UnmarshalJSON(b []byte) (err error) { var cidr string err = json.Unmarshal(b, &cidr) if err != nil { - return err + return errors.Wrap(err, "failed to Unmarshal string") } ip, net, err := net.ParseCIDR(cidr) if err != nil { - return err + return errors.Wrap(err, "failed to Parse cidr string to net.IPNet") } // This check is needed in order to work around a strange quirk in the Go diff --git a/pkg/rhcos/ami.go b/pkg/rhcos/ami.go index ba382769d60..462d59026d1 100644 --- a/pkg/rhcos/ami.go +++ b/pkg/rhcos/ami.go @@ -2,12 +2,12 @@ package rhcos import ( "context" - "fmt" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/pkg/errors" ) const ( @@ -18,7 +18,7 @@ const ( // AMI calculates a Red Hat CoreOS AMI. func AMI(ctx context.Context, channel, region string) (ami string, err error) { if channel != DefaultChannel { - return "", fmt.Errorf("channel %q is not yet supported", channel) + return "", errors.Errorf("channel %q is not yet supported", channel) } ssn := session.Must(session.NewSessionWithOptions(session.Options{ @@ -59,7 +59,7 @@ func AMI(ctx context.Context, channel, region string) (ami string, err error) { }, }) if err != nil { - return "", err + return "", errors.Wrap(err, "failed to describe AMIs") } var image *ec2.Image @@ -70,7 +70,7 @@ func AMI(ctx context.Context, channel, region string) (ami string, err error) { } nextCreated, err := time.Parse(time.RFC3339, *nextImage.CreationDate) if err != nil { - return "", err + return "", errors.Wrap(err, "failed to parse AMIs CreationDate to time.RFC3339") } if image == nil || nextCreated.After(created) { @@ -80,7 +80,7 @@ func AMI(ctx context.Context, channel, region string) (ami string, err error) { } if image == nil { - return "", fmt.Errorf("no RHCOS AMIs found in %s", region) + return "", errors.Errorf("no RHCOS AMIs found in %s", region) } return *image.ImageId, nil diff --git a/pkg/terraform/executor.go b/pkg/terraform/executor.go index 3c526b8101c..d55b4afb182 100644 --- a/pkg/terraform/executor.go +++ b/pkg/terraform/executor.go @@ -1,13 +1,12 @@ package terraform import ( - "errors" - "fmt" "os" "os/exec" "path/filepath" "runtime" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -40,7 +39,7 @@ func newExecutor() (*executor, error) { // Find the Terraform binary. binPath, err := tfBinaryPath() if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to get Terraform binary's path") } ex.binaryPath = binPath @@ -56,7 +55,7 @@ func (ex *executor) execute(clusterDir string, args ...string) error { // Prepare Terraform command by setting up the command, configuration, // and the working directory if clusterDir == "" { - return fmt.Errorf("clusterDir is unset. Quitting") + return errors.Errorf("clusterDir is unset. Quitting") } cmd := exec.Command(ex.binaryPath, args...) diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index 9bdd1171afb..3b39f8a73f3 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -3,18 +3,20 @@ package terraform import ( "fmt" "path" + + "github.com/pkg/errors" ) func terraformExec(clusterDir string, args ...string) error { // Create an executor ex, err := newExecutor() if err != nil { - return fmt.Errorf("could not create Terraform executor: %s", err) + return errors.Wrap(err, "failed to create Terraform executor") } err = ex.execute(clusterDir, args...) if err != nil { - return fmt.Errorf("failed to run Terraform: %s", err) + return errors.Wrap(err, "failed to execute Terraform") } return nil } diff --git a/pkg/types/config/cluster.go b/pkg/types/config/cluster.go index 10b6d4e15b3..70c42d50dd7 100644 --- a/pkg/types/config/cluster.go +++ b/pkg/types/config/cluster.go @@ -3,10 +3,10 @@ package config import ( "context" "encoding/json" - "fmt" "time" "github.com/coreos/tectonic-config/config/tectonic-network" + "github.com/pkg/errors" "gopkg.in/yaml.v2" "github.com/openshift/installer/pkg/rhcos" @@ -39,7 +39,7 @@ func (p *Platform) UnmarshalYAML(unmarshal func(interface{}) error) error { switch platform { case PlatformAWS, PlatformLibvirt, PlatformOpenStack: default: - return fmt.Errorf("invalid platform specified (%s); must be one of %s", platform, []Platform{PlatformAWS, PlatformLibvirt, PlatformOpenStack}) + return errors.Errorf("invalid platform specified (%s); must be one of %s", platform, []Platform{PlatformAWS, PlatformLibvirt, PlatformOpenStack}) } *p = platform @@ -116,13 +116,13 @@ func (c *Cluster) TFVars() (string, error) { // Fill in master ips if c.Platform == PlatformLibvirt { if err := c.Libvirt.TFVars(c.Master.Count, c.Worker.Count); err != nil { - return "", err + return "", errors.Wrap(err, "failed to create TFVars for libvirt platfrom") } } data, err := json.MarshalIndent(&c, "", " ") if err != nil { - return "", err + return "", errors.Wrap(err, "failed to marshal TFVars") } return string(data), nil @@ -144,7 +144,7 @@ func (c *Cluster) YAML() (string, error) { yaml, err := yaml.Marshal(c) if err != nil { - return "", err + return "", errors.Wrap(err, "failed to marshal config.Cluster") } return string(yaml), nil @@ -186,7 +186,7 @@ func ConvertInstallConfigToTFVars(cfg *types.InstallConfig, bootstrapIgn string, defer cancel() ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, cfg.Platform.AWS.Region) if err != nil { - return nil, fmt.Errorf("failed to determine default AMI: %v", err) + return nil, errors.Wrap(err, "failed to determine default AMI") } cluster.Platform = PlatformAWS @@ -268,14 +268,14 @@ func ConvertInstallConfigToTFVars(cfg *types.InstallConfig, bootstrapIgn string, } } default: - return nil, fmt.Errorf("unrecognized machine pool %q", m.Name) + return nil, errors.Errorf("unrecognized machine pool %q", m.Name) } } // Validate the TFVars. if err := cluster.ValidateAndLog(); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to validate TFVars") } return cluster, nil diff --git a/pkg/types/config/parser.go b/pkg/types/config/parser.go index 050b9c94485..cb8116b0970 100644 --- a/pkg/types/config/parser.go +++ b/pkg/types/config/parser.go @@ -2,11 +2,10 @@ package config import ( "context" - "errors" - "fmt" "io/ioutil" "time" + "github.com/pkg/errors" "gopkg.in/yaml.v2" "github.com/openshift/installer/pkg/rhcos" @@ -17,7 +16,7 @@ func ParseConfig(data []byte) (*Cluster, error) { cluster := defaultCluster if err := yaml.Unmarshal(data, &cluster); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to unmarshal to config.Cluster") } // Deprecated: remove after openshift/release is ported to pullSecret @@ -28,7 +27,7 @@ func ParseConfig(data []byte) (*Cluster, error) { data, err := ioutil.ReadFile(cluster.PullSecretPath) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read PullSecretPath") } cluster.PullSecret = string(data) cluster.PullSecretPath = "" @@ -40,7 +39,7 @@ func ParseConfig(data []byte) (*Cluster, error) { ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, cluster.AWS.Region) if err != nil { - return nil, fmt.Errorf("failed to determine default AMI: %v", err) + return nil, errors.Wrap(err, "failed to determine default AMI") } cluster.EC2AMIOverride = ami } @@ -52,7 +51,7 @@ func ParseConfig(data []byte) (*Cluster, error) { func ParseConfigFile(path string) (*Cluster, error) { data, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read file") } return ParseConfig(data) @@ -63,7 +62,7 @@ func ParseInternal(data []byte) (*Internal, error) { internal := &Internal{} if err := yaml.Unmarshal(data, internal); err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to unmarshal to config.Internal") } return internal, nil @@ -73,7 +72,7 @@ func ParseInternal(data []byte) (*Internal, error) { func ParseInternalFile(path string) (*Internal, error) { data, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read file") } return ParseInternal(data) diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 00000000000..835ba3e755c --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 00000000000..842ee80456d --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 00000000000..6b1f2891a5a --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +}