diff --git a/cmd/init.go b/cmd/init.go deleted file mode 100644 index 3d115b371..000000000 --- a/cmd/init.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2016-2018, Pulumi Corporation. All rights reserved. - -package cmd - -import ( - "fmt" - "os" - - "github.com/pulumi/pulumi/pkg/util/cmdutil" - "github.com/pulumi/pulumi/pkg/workspace" - "github.com/spf13/cobra" -) - -func newInitCmd() *cobra.Command { - var owner string - var name string - - cmd := &cobra.Command{ - Use: "init", - Short: "Initialize a new Pulumi repository", - Args: cmdutil.NoArgs, - Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return err - } - - repo, err := workspace.GetRepository(cwd) - if err != nil && err != workspace.ErrNoRepository { - return err - } - if err == workspace.ErrNoRepository { - // No existing repository, so we'll need to create one - repo = workspace.NewRepository(cwd) - - detectedOwner, detectedName, detectErr := detectOwnerAndName(cwd) - if detectErr != nil { - return detectErr - } - repo.Owner = detectedOwner - repo.Name = detectedName - } - - // explicit command line arguments should overwrite any existing values - if owner != "" { - repo.Owner = owner - } - - if name != "" { - repo.Name = name - } - - err = repo.Save() - if err != nil { - return err - } - - fmt.Printf("Initialized Pulumi repository\n") - - return nil - }), - } - - cmd.PersistentFlags().StringVar( - &owner, "owner", "", - "Override the repository owner; default is taken from current Git repository or username") - cmd.PersistentFlags().StringVar( - &name, "name", "", - "Override the repository name; default is taken from current Git repository or current working directory") - - return cmd -} diff --git a/cmd/pulumi.go b/cmd/pulumi.go index c89c6b6a7..5db8a24e3 100644 --- a/cmd/pulumi.go +++ b/cmd/pulumi.go @@ -92,7 +92,6 @@ func NewPulumiCmd() *cobra.Command { cmd.AddCommand(newCancelCmd()) cmd.AddCommand(newConfigCmd()) cmd.AddCommand(newDestroyCmd()) - cmd.AddCommand(newInitCmd()) cmd.AddCommand(newLoginCmd()) cmd.AddCommand(newLogoutCmd()) cmd.AddCommand(newLogsCmd()) diff --git a/cmd/util.go b/cmd/util.go index 66c75ee2d..b103cbc3e 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -9,18 +9,14 @@ import ( "os" "os/exec" "os/signal" - "os/user" - "path" "path/filepath" "sort" - "strings" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "golang.org/x/crypto/ssh/terminal" survey "gopkg.in/AlecAivazis/survey.v1" surveycore "gopkg.in/AlecAivazis/survey.v1/core" - git "gopkg.in/src-d/go-git.v4" "github.com/pulumi/pulumi/pkg/backend" "github.com/pulumi/pulumi/pkg/backend/cloud" @@ -32,7 +28,7 @@ import ( "github.com/pulumi/pulumi/pkg/util/cancel" "github.com/pulumi/pulumi/pkg/util/cmdutil" "github.com/pulumi/pulumi/pkg/util/contract" - "github.com/pulumi/pulumi/pkg/util/fsutil" + "github.com/pulumi/pulumi/pkg/util/gitutil" "github.com/pulumi/pulumi/pkg/util/logging" "github.com/pulumi/pulumi/pkg/workspace" ) @@ -232,90 +228,6 @@ func chooseStack(b backend.Backend, offerNew bool) (backend.Stack, error) { return stacks[option], nil } -func detectOwnerAndName(dir string) (string, string, error) { - owner, repo, err := getGitHubProjectForOrigin(dir) - if err == nil { - return owner, repo, nil - } - - user, err := user.Current() - if err != nil { - return "", "", err - } - - return user.Username, filepath.Base(dir), nil -} - -// getGitRepository returns the git repository by walking up from the provided directory. -// If no repository is found, will return (nil, nil). -func getGitRepository(dir string) (*git.Repository, error) { - gitRoot, err := fsutil.WalkUp(dir, func(s string) bool { return filepath.Base(s) == ".git" }, nil) - if err != nil { - return nil, errors.Wrapf(err, "searching for git repository from %v", dir) - } - if gitRoot == "" { - return nil, nil - } - - // Open the git repo in the .git folder's parent, not the .git folder itself. - repo, err := git.PlainOpen(path.Join(gitRoot, "..")) - if err == git.ErrRepositoryNotExists { - return nil, nil - } - if err != nil { - return nil, errors.Wrap(err, "reading git repository") - } - return repo, nil -} - -func getGitHubProjectForOrigin(dir string) (string, string, error) { - repo, err := getGitRepository(dir) - if repo == nil { - return "", "", fmt.Errorf("no git repository found from %v", dir) - } - if err != nil { - return "", "", err - } - return getGitHubProjectForOriginByRepo(repo) -} - -// Returns the GitHub login, and GitHub repo name if the "origin" remote is -// a GitHub URL. -func getGitHubProjectForOriginByRepo(repo *git.Repository) (string, string, error) { - remote, err := repo.Remote("origin") - if err != nil { - return "", "", errors.Wrap(err, "could not read origin information") - } - - remoteURL := "" - if len(remote.Config().URLs) > 0 { - remoteURL = remote.Config().URLs[0] - } - project := "" - - const GitHubSSHPrefix = "git@github.com:" - const GitHubHTTPSPrefix = "https://github.com/" - const GitHubRepositorySuffix = ".git" - - if strings.HasPrefix(remoteURL, GitHubSSHPrefix) { - project = trimGitRemoteURL(remoteURL, GitHubSSHPrefix, GitHubRepositorySuffix) - } else if strings.HasPrefix(remoteURL, GitHubHTTPSPrefix) { - project = trimGitRemoteURL(remoteURL, GitHubHTTPSPrefix, GitHubRepositorySuffix) - } - - split := strings.Split(project, "/") - - if len(split) != 2 { - return "", "", errors.Errorf("could not detect GitHub project from url: %v", remote) - } - - return split[0], split[1], nil -} - -func trimGitRemoteURL(url string, prefix string, suffix string) string { - return strings.TrimSuffix(strings.TrimPrefix(url, prefix), suffix) -} - // readProject attempts to detect and read the project for the current workspace. If an error occurs, it will be // printed to Stderr, and the returned value will be nil. If the project is successfully detected and read, it // is returned along with the path to its containing directory, which will be used as the root of the project's @@ -424,7 +336,7 @@ func getUpdateMetadata(msg, root string) (backend.UpdateMetadata, error) { } // Gather git-related data as appropriate. (Returns nil, nil if no repo found.) - repo, err := getGitRepository(root) + repo, err := gitutil.GetGitRepository(root) if err != nil { cmdutil.Diag().Warningf(diag.Message("", "could not detect Git repository: %v"), err) } @@ -434,7 +346,7 @@ func getUpdateMetadata(msg, root string) (backend.UpdateMetadata, error) { } // GitHub repo slug if applicable. We don't require GitHub, so swallow errors. - ghLogin, ghRepo, err := getGitHubProjectForOriginByRepo(repo) + ghLogin, ghRepo, err := gitutil.GetGitHubProjectForOriginByRepo(repo) if err != nil { cmdutil.Diag().Warningf(diag.Message("", "could not detect GitHub project information: %v"), err) } else { diff --git a/pkg/backend/cloud/backend.go b/pkg/backend/cloud/backend.go index 9f39bce5b..da49ead25 100644 --- a/pkg/backend/cloud/backend.go +++ b/pkg/backend/cloud/backend.go @@ -255,24 +255,10 @@ func cloudConsoleURL(cloudURL string, paths ...string) string { return cloudURL[:ix] + path.Join(append([]string{cloudURL[ix+len(defaultAPIURLPrefix):]}, paths...)...) } -// cloudConsoleProjectPath returns the project path components for getting to a stack in the cloud console. This path -// must, of course, be combined with the actual console base URL by way of the CloudConsoleURL function above. -func (b *cloudBackend) cloudConsoleProjectPath(projID client.ProjectIdentifier) string { - // When projID.Repository is the empty string, we are using the new identity model. In this case, the service - // does not include project or repository information in URLS, so the "project path" is simply the owner. - // - // TODO(ellismg)[pulumi/pulumi#1241] Clean this up once we remove pulumi init - if projID.Repository == "" { - return projID.Owner - } - - return path.Join(projID.Owner, projID.Repository, projID.Project) -} - // CloudConsoleStackPath returns the stack path components for getting to a stack in the cloud console. This path // must, of coursee, be combined with the actual console base URL by way of the CloudConsoleURL function above. func (b *cloudBackend) cloudConsoleStackPath(stackID client.StackIdentifier) string { - return path.Join(b.cloudConsoleProjectPath(stackID.ProjectIdentifier), stackID.Stack) + return path.Join(stackID.Owner, stackID.Stack) } // Logout logs out of the target cloud URL. @@ -370,15 +356,6 @@ type CreateStackOptions struct { CloudName string } -func ownerFromRef(stackRef backend.StackReference) string { - if r, ok := stackRef.(cloudBackendReference); ok { - return r.owner - } - - contract.Failf("bad StackReference type") - return "" -} - func (b *cloudBackend) CreateStack(ctx context.Context, stackRef backend.StackReference, opts interface{}) (backend.Stack, error) { @@ -391,7 +368,7 @@ func (b *cloudBackend) CreateStack(ctx context.Context, stackRef backend.StackRe return nil, errors.New("expected a CloudStackOptions value for opts parameter") } - project, err := b.getCloudProjectIdentifier(ownerFromRef(stackRef)) + stackID, err := b.getCloudStackIdentifier(stackRef) if err != nil { return nil, err } @@ -401,11 +378,11 @@ func (b *cloudBackend) CreateStack(ctx context.Context, stackRef backend.StackRe return nil, errors.Wrap(err, "error determining initial tags") } - apistack, err := b.client.CreateStack(ctx, project, cloudOpts.CloudName, string(stackRef.StackName()), tags) + apistack, err := b.client.CreateStack(ctx, stackID, cloudOpts.CloudName, tags) if err != nil { // If the status is 409 Conflict (stack already exists), return StackAlreadyExistsError. if errResp, ok := err.(*apitype.ErrorResponse); ok && errResp.Code == http.StatusConflict { - return nil, &backend.StackAlreadyExistsError{StackName: string(stackRef.StackName())} + return nil, &backend.StackAlreadyExistsError{StackName: stackID.Stack} } return nil, err } @@ -421,12 +398,7 @@ func (b *cloudBackend) CreateStack(ctx context.Context, stackRef backend.StackRe } func (b *cloudBackend) ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]backend.Stack, error) { - project, err := b.getCloudProjectIdentifier("") - if err != nil { - return nil, err - } - - stacks, err := b.client.ListStacks(ctx, project, projectFilter) + stacks, err := b.client.ListStacks(ctx, projectFilter) if err != nil { return nil, err } @@ -1072,59 +1044,22 @@ func (b *cloudBackend) ImportDeployment(ctx context.Context, stackRef backend.St return nil } -// getCloudProjectIdentifier returns information about the current repository and project, based on the current -// working directory. -func (b *cloudBackend) getCloudProjectIdentifier(owner string) (client.ProjectIdentifier, error) { - w, err := workspace.New() - if err != nil { - return client.ProjectIdentifier{}, err - } - - proj, err := workspace.DetectProject() - if err != nil { - return client.ProjectIdentifier{}, err - } - - repo := w.Repository() - - // If we have repository information (this is the case when `pulumi init` has been run, use that.) To support the - // old and new model, we either set the Repository field in ProjectIdentifer or keep it as the empty string. The - // client type uses this to decide what REST endpoints to hit. - // - // TODO(ellismg)[pulumi/pulumi#1241] Clean this up once we remove pulumi init - if repo != nil { - return client.ProjectIdentifier{ - Owner: repo.Owner, - Repository: repo.Name, - Project: string(proj.Name), - }, nil - } +// getCloudStackIdentifier returns information about the given stack in the current repository and project, based on +// the current working directory. +func (b *cloudBackend) getCloudStackIdentifier(stackRef backend.StackReference) (client.StackIdentifier, error) { + owner := stackRef.(cloudBackendReference).owner + var err error if owner == "" { owner, err = b.client.GetPulumiAccountName(context.Background()) if err != nil { - return client.ProjectIdentifier{}, err + return client.StackIdentifier{}, err } } - // Otherwise, we are on the new plan. - return client.ProjectIdentifier{ - Owner: owner, - Project: string(proj.Name), - }, nil -} - -// getCloudStackIdentifier returns information about the given stack in the current repository and project, based on -// the current working directory. -func (b *cloudBackend) getCloudStackIdentifier(stackRef backend.StackReference) (client.StackIdentifier, error) { - project, err := b.getCloudProjectIdentifier(ownerFromRef(stackRef)) - if err != nil { - return client.StackIdentifier{}, errors.Wrap(err, "failed to detect project") - } - return client.StackIdentifier{ - ProjectIdentifier: project, - Stack: string(stackRef.StackName()), + Owner: owner, + Stack: string(stackRef.StackName()), }, nil } diff --git a/pkg/backend/cloud/client/api.go b/pkg/backend/cloud/client/api.go index 7c7aefa5f..98471715e 100644 --- a/pkg/backend/cloud/client/api.go +++ b/pkg/backend/cloud/client/api.go @@ -35,18 +35,9 @@ const ( UpdateKindImport UpdateKind = "import" ) -// ProjectIdentifier is the set of data needed to identify a Pulumi Cloud project. This the -// logical "home" of a stack on the Pulumi Cloud. -type ProjectIdentifier struct { - Owner string - Repository string - Project string -} - // StackIdentifier is the set of data needed to identify a Pulumi Cloud stack. type StackIdentifier struct { - ProjectIdentifier - + Owner string Stack string } diff --git a/pkg/backend/cloud/client/client.go b/pkg/backend/cloud/client/client.go index 5236b5011..5a0f634ef 100644 --- a/pkg/backend/cloud/client/client.go +++ b/pkg/backend/cloud/client/client.go @@ -67,29 +67,10 @@ func (pc *Client) updateRESTCall(ctx context.Context, method, path string, query return pulumiRESTCall(ctx, pc.apiURL, method, path, queryObj, reqObj, respObj, token, httpOptions) } -// getProjectPath returns the API path to for the given project with the given components joined with path separators -// and appended to the project root. -func getProjectPath(project ProjectIdentifier, components ...string) string { - contract.Assertf(project.Repository != "", "need repository in ProjectIdentifier") - - projectRoot := fmt.Sprintf("/api/orgs/%s/programs/%s/%s", project.Owner, project.Repository, project.Project) - return path.Join(append([]string{projectRoot}, components...)...) -} - // getStackPath returns the API path to for the given stack with the given components joined with path separators // and appended to the stack root. func getStackPath(stack StackIdentifier, components ...string) string { - - // When stack.Repository is not empty, we are on the old pulumi init based identity plan, and we hit different REST - // endpoints. - // - // TODO(ellismg)[pulumi/pulumi#1241] Clean this up once we remove pulumi init - if stack.Repository == "" { - return path.Join(append([]string{fmt.Sprintf("/api/stacks/%s/%s", stack.Owner, stack.Stack)}, components...)...) - } - - components = append([]string{"stacks", stack.Stack}, components...) - return getProjectPath(stack.ProjectIdentifier, components...) + return path.Join(append([]string{fmt.Sprintf("/api/stacks/%s/%s", stack.Owner, stack.Stack)}, components...)...) } // getUpdatePath returns the API path to for the given stack with the given components joined with path separators @@ -154,27 +135,19 @@ func (pc *Client) DownloadTemplate(ctx context.Context, name string) (io.ReadClo } // ListStacks lists all stacks for the indicated project. -func (pc *Client) ListStacks(ctx context.Context, project ProjectIdentifier, - projectFilter *tokens.PackageName) ([]apitype.Stack, error) { +func (pc *Client) ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]apitype.Stack, error) { // Query all stacks for the project on Pulumi. var stacks []apitype.Stack + var queryFilter interface{} + if projectFilter != nil { + queryFilter = struct { + ProjectFilter string `url:"project"` + }{ProjectFilter: string(*projectFilter)} + } - if project.Repository != "" { - if err := pc.restCall(ctx, "GET", getProjectPath(project, "stacks"), nil, nil, &stacks); err != nil { - return nil, err - } - } else { - var queryFilter interface{} - if projectFilter != nil { - queryFilter = struct { - ProjectFilter string `url:"project"` - }{ProjectFilter: project.Project} - } - - if err := pc.restCall(ctx, "GET", "/api/user/stacks", queryFilter, nil, &stacks); err != nil { - return nil, err - } + if err := pc.restCall(ctx, "GET", "/api/user/stacks", queryFilter, nil, &stacks); err != nil { + return nil, err } return stacks, nil @@ -223,38 +196,29 @@ func (pc *Client) GetStack(ctx context.Context, stackID StackIdentifier) (apityp // CreateStack creates a stack with the given cloud and stack name in the scope of the indicated project. func (pc *Client) CreateStack( - ctx context.Context, project ProjectIdentifier, cloudName string, stackName string, + ctx context.Context, stackID StackIdentifier, cloudName string, tags map[apitype.StackTagName]string) (apitype.Stack, error) { // Validate names and tags. - if err := backend.ValidateStackProperties(stackName, tags); err != nil { + if err := backend.ValidateStackProperties(stackID.Stack, tags); err != nil { return apitype.Stack{}, errors.Wrap(err, "validating stack properties") } stack := apitype.Stack{ - CloudName: cloudName, - StackName: tokens.QName(stackName), - OrgName: project.Owner, - RepoName: project.Repository, - ProjectName: project.Project, - Tags: tags, + CloudName: cloudName, + StackName: tokens.QName(stackID.Stack), + OrgName: stackID.Owner, + Tags: tags, } createStackReq := apitype.CreateStackRequest{ CloudName: cloudName, - StackName: stackName, + StackName: stackID.Stack, Tags: tags, } var createStackResp apitype.CreateStackResponseByName - if project.Repository != "" { - if err := pc.restCall( - ctx, "POST", getProjectPath(project, "stacks"), nil, &createStackReq, &createStackResp); err != nil { - return apitype.Stack{}, err - } - } else { - if err := pc.restCall( - ctx, "POST", fmt.Sprintf("/api/stacks/%s", project.Owner), nil, &createStackReq, &createStackResp); err != nil { - return apitype.Stack{}, err - } + if err := pc.restCall( + ctx, "POST", fmt.Sprintf("/api/stacks/%s", stackID.Owner), nil, &createStackReq, &createStackResp); err != nil { + return apitype.Stack{}, err } stack.CloudName = createStackResp.CloudName diff --git a/pkg/backend/stack.go b/pkg/backend/stack.go index dee1a814c..a34d7c88d 100644 --- a/pkg/backend/stack.go +++ b/pkg/backend/stack.go @@ -4,6 +4,7 @@ package backend import ( "context" + "path/filepath" "regexp" "github.com/pkg/errors" @@ -13,6 +14,7 @@ import ( "github.com/pulumi/pulumi/pkg/operations" "github.com/pulumi/pulumi/pkg/resource/config" "github.com/pulumi/pulumi/pkg/resource/deploy" + "github.com/pulumi/pulumi/pkg/util/gitutil" "github.com/pulumi/pulumi/pkg/workspace" ) @@ -105,17 +107,6 @@ func ImportStackDeployment(ctx context.Context, s Stack, deployment *apitype.Unt func GetStackTags() (map[apitype.StackTagName]string, error) { tags := make(map[apitype.StackTagName]string) - // Tags based on the workspace's repository. - w, err := workspace.New() - if err != nil { - return nil, err - } - repo := w.Repository() - if repo != nil { - tags[apitype.GitHubOwnerNameTag] = repo.Owner - tags[apitype.GitHubRepositoryNameTag] = repo.Name - } - // Tags based on Pulumi.yaml. projPath, err := workspace.DetectProjectPath() if err != nil { @@ -131,6 +122,12 @@ func GetStackTags() (map[apitype.StackTagName]string, error) { if proj.Description != nil { tags[apitype.ProjectDescriptionTag] = *proj.Description } + + if owner, repo, err := gitutil.GetGitHubProjectForOrigin(filepath.Dir(projPath)); err == nil { + tags[apitype.GitHubOwnerNameTag] = owner + tags[apitype.GitHubRepositoryNameTag] = repo + } + } return tags, nil diff --git a/pkg/testing/integration/pulumi.go b/pkg/testing/integration/pulumi.go index fc704b459..eb36824bf 100644 --- a/pkg/testing/integration/pulumi.go +++ b/pkg/testing/integration/pulumi.go @@ -1,7 +1,6 @@ package integration import ( - "encoding/json" "fmt" "io/ioutil" "os" @@ -25,29 +24,6 @@ func CreateBasicPulumiRepo(e *testing.Environment) { assert.NoError(e, err, "writing %s file", filePath) } -// GetRepository returns the contents of the workspace's repository settings file. Assumes the -// bookkeeping dir (.pulumi) is in the CWD. Any IO errors fails the test. -func GetRepository(e *testing.Environment) workspace.Repository { - relativePathToRepoFile := fmt.Sprintf("%s/%s", workspace.BookkeepingDir, workspace.RepoFile) - if !e.PathExists(relativePathToRepoFile) { - e.Fatalf("did not find .pulumi/settings.json") - } - - path := path.Join(e.CWD, relativePathToRepoFile) - contents, err := ioutil.ReadFile(path) - if err != nil { - e.Fatalf("error reading %s: %v", workspace.RepoFile, err) - } - - var repo workspace.Repository - err = json.Unmarshal(contents, &repo) - if err != nil { - e.Fatalf("error unmarshalling JSON: %v", err) - } - - return repo -} - // GetStacks returns the list of stacks and current stack by scraping `pulumi stack ls`. // Assumes .pulumi is in the current working directory. Fails the test on IO errors. func GetStacks(e *testing.Environment) ([]string, *string) { diff --git a/pkg/util/gitutil/git.go b/pkg/util/gitutil/git.go new file mode 100644 index 000000000..2ddae661a --- /dev/null +++ b/pkg/util/gitutil/git.go @@ -0,0 +1,86 @@ +// Copyright 2016-2018, Pulumi Corporation. All rights reserved. + +package gitutil + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "github.com/pulumi/pulumi/pkg/util/fsutil" + git "gopkg.in/src-d/go-git.v4" +) + +// GetGitRepository returns the git repository by walking up from the provided directory. +// If no repository is found, will return (nil, nil). +func GetGitRepository(dir string) (*git.Repository, error) { + gitRoot, err := fsutil.WalkUp(dir, func(s string) bool { return filepath.Base(s) == ".git" }, nil) + if err != nil { + return nil, errors.Wrapf(err, "searching for git repository from %v", dir) + } + if gitRoot == "" { + return nil, nil + } + + // Open the git repo in the .git folder's parent, not the .git folder itself. + repo, err := git.PlainOpen(path.Join(gitRoot, "..")) + if err == git.ErrRepositoryNotExists { + return nil, nil + } + if err != nil { + return nil, errors.Wrap(err, "reading git repository") + } + return repo, nil +} + +// GetGitHubProjectForOrigin returns the GitHub login, and GitHub repo name if the "origin" remote is +// a GitHub URL. +func GetGitHubProjectForOrigin(dir string) (string, string, error) { + repo, err := GetGitRepository(dir) + if repo == nil { + return "", "", fmt.Errorf("no git repository found from %v", dir) + } + if err != nil { + return "", "", err + } + return GetGitHubProjectForOriginByRepo(repo) +} + +// GetGitHubProjectForOriginByRepo returns the GitHub login, and GitHub repo name if the "origin" remote is +// a GitHub URL. +func GetGitHubProjectForOriginByRepo(repo *git.Repository) (string, string, error) { + remote, err := repo.Remote("origin") + if err != nil { + return "", "", errors.Wrap(err, "could not read origin information") + } + + remoteURL := "" + if len(remote.Config().URLs) > 0 { + remoteURL = remote.Config().URLs[0] + } + project := "" + + const GitHubSSHPrefix = "git@github.com:" + const GitHubHTTPSPrefix = "https://github.com/" + const GitHubRepositorySuffix = ".git" + + if strings.HasPrefix(remoteURL, GitHubSSHPrefix) { + project = trimGitRemoteURL(remoteURL, GitHubSSHPrefix, GitHubRepositorySuffix) + } else if strings.HasPrefix(remoteURL, GitHubHTTPSPrefix) { + project = trimGitRemoteURL(remoteURL, GitHubHTTPSPrefix, GitHubRepositorySuffix) + } + + split := strings.Split(project, "/") + + if len(split) != 2 { + return "", "", errors.Errorf("could not detect GitHub project from url: %v", remote) + } + + return split[0], split[1], nil +} + +func trimGitRemoteURL(url string, prefix string, suffix string) string { + return strings.TrimSuffix(strings.TrimPrefix(url, prefix), suffix) +} diff --git a/pkg/workspace/paths.go b/pkg/workspace/paths.go index aebf4c3d0..46b5306ae 100644 --- a/pkg/workspace/paths.go +++ b/pkg/workspace/paths.go @@ -63,7 +63,7 @@ func DetectProjectStackPath(stackName tokens.QName) (string, error) { // hierarchy. If no project is found, an empty path is returned. func DetectProjectPathFrom(path string) (string, error) { return fsutil.WalkUp(path, isProject, func(s string) bool { - return !isRepositoryFolder(filepath.Join(s, BookkeepingDir)) + return true }) } @@ -115,22 +115,6 @@ func SaveProjectStack(stackName tokens.QName, stack *ProjectStack) error { return stack.Save(path) } -func isGitFolder(path string) bool { - info, err := os.Stat(path) - return err == nil && info.IsDir() && info.Name() == GitDir -} - -func isRepositoryFolder(path string) bool { - info, err := os.Stat(path) - if err == nil && info.IsDir() && info.Name() == BookkeepingDir { - // make sure it has a settings.json file in it - info, err := os.Stat(filepath.Join(path, RepoFile)) - return err == nil && !info.IsDir() - } - - return false -} - // isProject returns true if the path references what appears to be a valid project. If problems are detected -- like // an incorrect extension -- they are logged to the provided diag.Sink (if non-nil). func isProject(path string) bool { diff --git a/pkg/workspace/repository.go b/pkg/workspace/repository.go deleted file mode 100644 index 0c883d7a9..000000000 --- a/pkg/workspace/repository.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2016-2018, Pulumi Corporation. All rights reserved. - -package workspace - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/util/contract" - "github.com/pulumi/pulumi/pkg/util/fsutil" -) - -type Repository struct { - Owner string `json:"owner" yaml:"owner"` // the owner of this repository - Name string `json:"name" yaml:"name"` // the name of the repository - root string // storage location -} - -func (r *Repository) Save() error { - contract.Requiref(r.root != "", "r", "needs non empty root") - - b, err := json.MarshalIndent(r, "", " ") - if err != nil { - return err - } - - // nolint: gas - err = os.MkdirAll(r.root, 0755) - if err != nil { - return err - } - - return ioutil.WriteFile(filepath.Join(r.root, RepoFile), b, 0644) -} - -func NewRepository(root string) *Repository { - return &Repository{root: getDotPulumiDirectoryPath(root)} -} - -var ErrNoRepository = errors.New("no repository detected; did you forget to run 'pulumi init'?") - -func GetRepository(root string) (*Repository, error) { - dotPulumiPath := getDotPulumiDirectoryPath(root) - - repofilePath := filepath.Join(dotPulumiPath, RepoFile) - - b, err := ioutil.ReadFile(repofilePath) - if os.IsNotExist(err) { - return nil, ErrNoRepository - } else if err != nil { - return nil, err - } - - var repo Repository - err = json.Unmarshal(b, &repo) - if err != nil { - return nil, err - } - - if repo.Owner == "" { - return nil, errors.New("invalid repo.json file, missing name property") - } - - if repo.Name == "" { - return nil, errors.New("invalid repo.json file, missing name property") - } - - repo.root = dotPulumiPath - - return &repo, nil -} - -func getDotPulumiDirectoryPath(dir string) string { - // First, let's look to see if there's an existing .pulumi folder - dotpulumipath, _ := fsutil.WalkUp(dir, isRepositoryFolder, nil) - if dotpulumipath != "" { - return dotpulumipath - } - - // If there's a .git folder, put .pulumi there - dotgitpath, _ := fsutil.WalkUp(dir, isGitFolder, nil) - if dotgitpath != "" { - return filepath.Join(filepath.Dir(dotgitpath), ".pulumi") - } - - return filepath.Join(dir, ".pulumi") -} diff --git a/pkg/workspace/workspace.go b/pkg/workspace/workspace.go index 0a4382e7a..5494fb517 100644 --- a/pkg/workspace/workspace.go +++ b/pkg/workspace/workspace.go @@ -21,16 +21,14 @@ import ( // W offers functionality for interacting with Pulumi workspaces. type W interface { - Settings() *Settings // returns a mutable pointer to the optional workspace settings info. - Repository() *Repository // (optional) returns the repository this project belongs to. - Save() error // saves any modifications to the workspace. + Settings() *Settings // returns a mutable pointer to the optional workspace settings info. + Save() error // saves any modifications to the workspace. } type projectWorkspace struct { name tokens.PackageName // the package this workspace is associated with. project string // the path to the Pulumi.[yaml|json] file for this project. settings *Settings // settings for this workspace. - repo *Repository // the repo this workspace is associated with. } var cache = make(map[string]W) @@ -75,13 +73,6 @@ func NewFrom(dir string) (W, error) { return w, nil } - repo, err := GetRepository(dir) - if err == ErrNoRepository { - repo = nil - } else if err != nil { - return nil, err - } - path, err := DetectProjectPathFrom(dir) if err != nil { return nil, err @@ -97,7 +88,6 @@ func NewFrom(dir string) (W, error) { w := &projectWorkspace{ name: proj.Name, project: path, - repo: repo, } err = w.readSettings() @@ -117,10 +107,6 @@ func (pw *projectWorkspace) Settings() *Settings { return pw.settings } -func (pw *projectWorkspace) Repository() *Repository { - return pw.repo -} - func (pw *projectWorkspace) Save() error { // let's remove all the empty entries from the config array for k, v := range pw.settings.ConfigDeprecated { diff --git a/tests/init_test.go b/tests/init_test.go deleted file mode 100644 index 89c78423f..000000000 --- a/tests/init_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2016-2018, Pulumi Corporation. All rights reserved. - -package tests - -import ( - "os" - "path" - "testing" - - ptesting "github.com/pulumi/pulumi/pkg/testing" - "github.com/pulumi/pulumi/pkg/testing/integration" - "github.com/pulumi/pulumi/pkg/workspace" - "github.com/stretchr/testify/assert" -) - -func TestPulumiInit(t *testing.T) { - t.Run("SanityTest", func(t *testing.T) { - e := ptesting.NewEnvironment(t) - defer func() { - if !t.Failed() { - e.DeleteEnvironment() - } - }() - - // With a .git folder in the test root, `pulumi init` sets up shop there. - const dirName = workspace.BookkeepingDir - e.RunCommand("git", "init") - assert.False(t, e.PathExists(dirName), "expecting no %s folder yet", dirName) - e.RunCommand("pulumi", "init") - assert.True(t, e.PathExists(dirName), "expecting %s folder to be created", dirName) - }) - - t.Run("WalkUpToGitFolder", func(t *testing.T) { - e := ptesting.NewEnvironment(t) - defer func() { - if !t.Failed() { - e.DeleteEnvironment() - } - }() - - // Create a git repo in the root. - e.RunCommand("git", "init") - assert.True(t, e.PathExists(".git"), "expecting .git folder") - - // Create a subdirectory and CD into it., - subdir := path.Join(e.RootPath, "/foo/bar/baz/") - err := os.MkdirAll(subdir, os.ModePerm) - assert.NoError(t, err, "error creating subdirectory") - e.CWD = subdir - - // Confirm we are in the new location (no .git folder found.) - assert.False(t, e.PathExists(".git"), "expecting no .git folder (in new dir)") - - // pulumi init won't create the folder here, but rather along side .git. - const dirName = workspace.BookkeepingDir - assert.False(t, e.PathExists(dirName), "expecting no %s folder", dirName) - e.RunCommand("pulumi", "init") - assert.False(t, e.PathExists(dirName), "expecting no %s folder. still.", dirName) - - e.CWD = e.RootPath - assert.True(t, e.PathExists(dirName), "expecting %s folder to exist (next to .git)", dirName) - }) - - t.Run("DefaultRepositoryInfo", func(t *testing.T) { - e := ptesting.NewEnvironment(t) - defer func() { - if !t.Failed() { - e.DeleteEnvironment() - } - }() - - e.RunCommand("git", "init") - e.RunCommand("pulumi", "init") - - // Defaults - repo := integration.GetRepository(e) - testRootName := path.Base(e.RootPath) - assert.Equal(t, os.Getenv("USER"), repo.Owner) - assert.Equal(t, testRootName, repo.Name) - }) - - t.Run("ReadRemoteInfo", func(t *testing.T) { - e := ptesting.NewEnvironment(t) - defer func() { - if !t.Failed() { - e.DeleteEnvironment() - } - }() - - e.RunCommand("git", "init") - e.RunCommand("git", "remote", "add", "not-origin", "git@github.com:moolumi/pasture.git") - e.RunCommand("git", "remote", "add", "origin", "git@github.com:pulumi/pulumi-cloud.git") - e.RunCommand("pulumi", "init") - - // We pick up the settings from "origin", not any other remote name. - repo := integration.GetRepository(e) - assert.Equal(t, "pulumi", repo.Owner) - assert.Equal(t, "pulumi-cloud", repo.Name) - }) -} diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 6045f7afa..09416afb8 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -100,7 +100,6 @@ func TestStackTagValidation(t *testing.T) { } }() e.RunCommand("git", "init") - e.RunCommand("pulumi", "init") e.ImportDirectory("stack_project_name") e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL()) @@ -120,7 +119,6 @@ func TestStackTagValidation(t *testing.T) { } }() e.RunCommand("git", "init") - e.RunCommand("pulumi", "init") e.ImportDirectory("stack_project_name") e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL()) @@ -145,7 +143,6 @@ func TestStackTagValidation(t *testing.T) { } }() e.RunCommand("git", "init") - e.RunCommand("pulumi", "init") e.ImportDirectory("stack_project_name") e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())