github.com/opentofu/opentofu@v1.7.1/internal/cloud/cloudplan/saved_plan.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  package cloudplan
     6  
     7  import (
     8  	"encoding/json"
     9  	"errors"
    10  	"io"
    11  	"os"
    12  	"strings"
    13  )
    14  
    15  var ErrInvalidRemotePlanFormat = errors.New("invalid remote plan format, must be 1")
    16  var ErrInvalidRunID = errors.New("invalid run ID")
    17  var ErrInvalidHostname = errors.New("invalid hostname")
    18  
    19  type SavedPlanBookmark struct {
    20  	RemotePlanFormat int    `json:"remote_plan_format"`
    21  	RunID            string `json:"run_id"`
    22  	Hostname         string `json:"hostname"`
    23  }
    24  
    25  func NewSavedPlanBookmark(runID, hostname string) SavedPlanBookmark {
    26  	return SavedPlanBookmark{
    27  		RemotePlanFormat: 1,
    28  		RunID:            runID,
    29  		Hostname:         hostname,
    30  	}
    31  }
    32  
    33  func LoadSavedPlanBookmark(filepath string) (SavedPlanBookmark, error) {
    34  	bookmark := SavedPlanBookmark{}
    35  
    36  	file, err := os.Open(filepath)
    37  	if err != nil {
    38  		return bookmark, err
    39  	}
    40  	defer file.Close()
    41  
    42  	data, err := io.ReadAll(file)
    43  	if err != nil {
    44  		return bookmark, err
    45  	}
    46  
    47  	err = json.Unmarshal(data, &bookmark)
    48  	if err != nil {
    49  		return bookmark, err
    50  	}
    51  
    52  	// Note that these error cases are somewhat ambiguous, but they *likely*
    53  	// mean we're not looking at a saved plan bookmark at all. Since we're not
    54  	// certain about the format at this point, it doesn't quite make sense to
    55  	// emit a "known file type but bad" error struct the way we do over in the
    56  	// planfile and statefile packages.
    57  	if bookmark.RemotePlanFormat != 1 {
    58  		return bookmark, ErrInvalidRemotePlanFormat
    59  	} else if bookmark.Hostname == "" {
    60  		return bookmark, ErrInvalidHostname
    61  	} else if bookmark.RunID == "" || !strings.HasPrefix(bookmark.RunID, "run-") {
    62  		return bookmark, ErrInvalidRunID
    63  	}
    64  
    65  	return bookmark, err
    66  }
    67  
    68  func (s *SavedPlanBookmark) Save(filepath string) error {
    69  	data, _ := json.Marshal(s)
    70  
    71  	err := os.WriteFile(filepath, data, 0644)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	return nil
    77  }