github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/packages/import.go (about) 1 package packages 2 3 import ( 4 "os" 5 6 "github.com/ActiveState/cli/internal/analytics" 7 "github.com/ActiveState/cli/internal/errs" 8 "github.com/ActiveState/cli/internal/keypairs" 9 "github.com/ActiveState/cli/internal/locale" 10 "github.com/ActiveState/cli/internal/logging" 11 "github.com/ActiveState/cli/internal/output" 12 "github.com/ActiveState/cli/internal/primer" 13 "github.com/ActiveState/cli/internal/prompt" 14 "github.com/ActiveState/cli/internal/runbits/rationalize" 15 "github.com/ActiveState/cli/internal/runbits/runtime" 16 "github.com/ActiveState/cli/pkg/localcommit" 17 "github.com/ActiveState/cli/pkg/platform/api" 18 "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" 19 "github.com/ActiveState/cli/pkg/platform/api/reqsimport" 20 "github.com/ActiveState/cli/pkg/platform/authentication" 21 "github.com/ActiveState/cli/pkg/platform/model" 22 "github.com/ActiveState/cli/pkg/platform/model/buildplanner" 23 "github.com/ActiveState/cli/pkg/platform/runtime/buildexpression" 24 "github.com/ActiveState/cli/pkg/platform/runtime/target" 25 "github.com/ActiveState/cli/pkg/project" 26 ) 27 28 const ( 29 defaultImportFile = "requirements.txt" 30 ) 31 32 type configurable interface { 33 keypairs.Configurable 34 } 35 36 // Confirmer describes the behavior required to prompt a user for confirmation. 37 type Confirmer interface { 38 Confirm(title, msg string, defaultOpt *bool) (bool, error) 39 } 40 41 // ChangesetProvider describes the behavior required to convert some file data 42 // into a changeset. 43 type ChangesetProvider interface { 44 Changeset(contents []byte, lang string) (model.Changeset, error) 45 } 46 47 // ImportRunParams tracks the info required for running Import. 48 type ImportRunParams struct { 49 FileName string 50 Language string 51 NonInteractive bool 52 } 53 54 // NewImportRunParams prepares the info required for running Import with default 55 // values. 56 func NewImportRunParams() *ImportRunParams { 57 return &ImportRunParams{ 58 FileName: defaultImportFile, 59 } 60 } 61 62 // Import manages the importing execution context. 63 type Import struct { 64 auth *authentication.Auth 65 out output.Outputer 66 prompt.Prompter 67 proj *project.Project 68 cfg configurable 69 analytics analytics.Dispatcher 70 svcModel *model.SvcModel 71 } 72 73 type primeable interface { 74 primer.Outputer 75 primer.Prompter 76 primer.Projecter 77 primer.Auther 78 primer.Configurer 79 primer.Analyticer 80 primer.SvcModeler 81 } 82 83 // NewImport prepares an importation execution context for use. 84 func NewImport(prime primeable) *Import { 85 return &Import{ 86 prime.Auth(), 87 prime.Output(), 88 prime.Prompt(), 89 prime.Project(), 90 prime.Config(), 91 prime.Analytics(), 92 prime.SvcModel(), 93 } 94 } 95 96 // Run executes the import behavior. 97 func (i *Import) Run(params *ImportRunParams) error { 98 logging.Debug("ExecuteImport") 99 100 if i.proj == nil { 101 return rationalize.ErrNoProject 102 } 103 104 i.out.Notice(locale.Tr("operating_message", i.proj.NamespaceString(), i.proj.Dir())) 105 106 if params.FileName == "" { 107 params.FileName = defaultImportFile 108 } 109 110 latestCommit, err := localcommit.Get(i.proj.Dir()) 111 if err != nil { 112 return locale.WrapError(err, "package_err_cannot_obtain_commit") 113 } 114 115 reqs, err := fetchCheckpoint(&latestCommit, i.auth) 116 if err != nil { 117 return locale.WrapError(err, "package_err_cannot_fetch_checkpoint") 118 } 119 120 lang, err := model.CheckpointToLanguage(reqs, i.auth) 121 if err != nil { 122 return locale.WrapExternalError(err, "err_import_language", "Your project does not have a language associated with it, please add a language first.") 123 } 124 125 changeset, err := fetchImportChangeset(reqsimport.Init(), params.FileName, lang.Name) 126 if err != nil { 127 return errs.Wrap(err, "Could not import changeset") 128 } 129 130 bp := buildplanner.NewBuildPlannerModel(i.auth) 131 be, err := bp.GetBuildExpression(latestCommit.String()) 132 if err != nil { 133 return locale.WrapError(err, "err_cannot_get_build_expression", "Could not get build expression") 134 } 135 136 if err := applyChangeset(changeset, be); err != nil { 137 return locale.WrapError(err, "err_cannot_apply_changeset", "Could not apply changeset") 138 } 139 140 if err := be.SetDefaultTimestamp(); err != nil { 141 return locale.WrapError(err, "err_cannot_set_timestamp", "Could not set timestamp") 142 } 143 144 msg := locale.T("commit_reqstext_message") 145 commitID, err := bp.StageCommit(buildplanner.StageCommitParams{ 146 Owner: i.proj.Owner(), 147 Project: i.proj.Name(), 148 ParentCommit: latestCommit.String(), 149 Description: msg, 150 Expression: be, 151 }) 152 if err != nil { 153 return locale.WrapError(err, "err_commit_changeset", "Could not commit import changes") 154 } 155 156 if err := localcommit.Set(i.proj.Dir(), commitID.String()); err != nil { 157 return locale.WrapError(err, "err_package_update_commit_id") 158 } 159 160 _, err = runtime.SolveAndUpdate(i.auth, i.out, i.analytics, i.proj, &commitID, target.TriggerImport, i.svcModel, i.cfg, runtime.OptOrderChanged) 161 return err 162 } 163 164 func fetchImportChangeset(cp ChangesetProvider, file string, lang string) (model.Changeset, error) { 165 data, err := os.ReadFile(file) 166 if err != nil { 167 return nil, locale.WrapExternalError(err, "err_reading_changeset_file", "Cannot read import file: {{.V0}}", err.Error()) 168 } 169 170 changeset, err := cp.Changeset(data, lang) 171 if err != nil { 172 return nil, locale.WrapError(err, "err_obtaining_change_request", "Could not process change set: {{.V0}}.", api.ErrorMessageFromPayload(err)) 173 } 174 175 return changeset, err 176 } 177 178 func applyChangeset(changeset model.Changeset, be *buildexpression.BuildExpression) error { 179 for _, change := range changeset { 180 var expressionOperation types.Operation 181 switch change.Operation { 182 case string(model.OperationAdded): 183 expressionOperation = types.OperationAdded 184 case string(model.OperationRemoved): 185 expressionOperation = types.OperationRemoved 186 case string(model.OperationUpdated): 187 expressionOperation = types.OperationUpdated 188 } 189 190 req := types.Requirement{ 191 Name: change.Requirement, 192 Namespace: change.Namespace, 193 } 194 195 for _, constraint := range change.VersionConstraints { 196 req.VersionRequirement = append(req.VersionRequirement, types.VersionRequirement{ 197 types.VersionRequirementComparatorKey: constraint.Comparator, 198 types.VersionRequirementVersionKey: constraint.Version, 199 }) 200 } 201 202 if err := be.UpdateRequirement(expressionOperation, req); err != nil { 203 return errs.Wrap(err, "Could not update build expression") 204 } 205 } 206 207 return nil 208 }