package publish import ( "encoding/json" "fmt" "io" "log/slog" "net/http" "net/url" "os" ) type Version struct { Version string `json:"version"` Protocols []string `json:"protocols"` Platforms []Platform `json:"platforms"` } type Platform struct { OS string `json:"os" yaml:"os"` Arch string `json:"arch" yaml:"arch"` } type Data struct { Id string `json:"id,omitempty"` Versions []Version `json:"versions"` } func (d *Data) WriteToFile(filePath string) error { // TODO: make it variable d.Id = "tfregistry.sysops.stackit.rocks/mhenselin/stackitprivatepreview" jsonString, err := json.Marshal(d) if err != nil { return fmt.Errorf("error encoding data: %w", err) } //nolint:gosec // this file is not sensitive, so we can use os.ModePerm err = os.WriteFile( filePath, jsonString, os.ModePerm, ) if err != nil { return fmt.Errorf("error writing data: %w", err) } return nil } func (d *Data) AddVersion(v Version) error { var newVersions []Version for _, ver := range d.Versions { if ver.Version != v.Version { newVersions = append(newVersions, ver) } } newVersions = append(newVersions, v) d.Versions = newVersions return nil } func (d *Data) Validate() error { for _, v := range d.Versions { err := v.Validate() if err != nil { return err } } return nil } func (d *Data) LoadFromFile(filePath string) error { plan, err := os.ReadFile(filePath) if err != nil { return err } err = json.Unmarshal(plan, &d) if err != nil { return err } return nil } func (d *Data) LoadFromUrl(uri string) error { u, err := url.ParseRequestURI(uri) if err != nil { return err } file, err := os.CreateTemp("", "versions.*.json") if err != nil { return err } defer func(name string) { //nolint:gosec // The file path is generated by os.CreateTemp and is not user-controllable err := os.Remove(name) if err != nil { slog.Error("failed to remove temporary file", slog.Any("err", err)) } }(file.Name()) // Clean up err = DownloadFile( u.String(), file.Name(), ) if err != nil { return err } return d.LoadFromFile(file.Name()) } func (v *Version) Validate() error { slog.Warn("validation needs to be implemented") return nil } func (v *Version) AddPlatform(p Platform) error { if p.OS == "" || p.Arch == "" { return fmt.Errorf("OS and Arch MUST NOT be empty") } v.Platforms = append(v.Platforms, p) return nil } func (v *Version) AddProtocol(p string) error { if p == "" { return fmt.Errorf("protocol MUST NOT be empty") } v.Protocols = append(v.Protocols, p) return nil } // DownloadFile will download a url and store it in local filepath. // It writes to the destination file as it downloads it, without // loading the entire file into memory. func DownloadFile(urlValue, filepath string) error { // Create the file //nolint:gosec // path traversal is not a concern here, as the filepath is generated by us and not user input out, err := os.Create(filepath) if err != nil { return err } defer func(out *os.File) { err := out.Close() if err != nil { slog.Error("failed to close file", slog.Any("err", err)) } }(out) // Get the data //nolint:gosec,bodyclose // this is a controlled URL, not user input resp, err := http.Get(urlValue) if err != nil { return err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) // Write the body to file _, err = io.Copy(out, resp.Body) if err != nil { return err } return nil }